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.MockApplication;
6   import com.atlassian.plugin.MockModuleDescriptor;
7   import com.atlassian.plugin.ModuleDescriptor;
8   import com.atlassian.plugin.ModuleDescriptorFactory;
9   import com.atlassian.plugin.Permissions;
10  import com.atlassian.plugin.Plugin;
11  import com.atlassian.plugin.PluginAccessor;
12  import com.atlassian.plugin.PluginArtifact;
13  import com.atlassian.plugin.PluginException;
14  import com.atlassian.plugin.PluginInformation;
15  import com.atlassian.plugin.PluginInstaller;
16  import com.atlassian.plugin.PluginParseException;
17  import com.atlassian.plugin.PluginRestartState;
18  import com.atlassian.plugin.PluginState;
19  import com.atlassian.plugin.descriptors.AbstractModuleDescriptor;
20  import com.atlassian.plugin.descriptors.MockUnusedModuleDescriptor;
21  import com.atlassian.plugin.descriptors.RequiresRestart;
22  import com.atlassian.plugin.event.PluginEventListener;
23  import com.atlassian.plugin.event.PluginEventManager;
24  import com.atlassian.plugin.event.events.PluginContainerUnavailableEvent;
25  import com.atlassian.plugin.event.events.PluginDisabledEvent;
26  import com.atlassian.plugin.event.events.PluginEnabledEvent;
27  import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
28  import com.atlassian.plugin.event.events.PluginModuleAvailableEvent;
29  import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
30  import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
31  import com.atlassian.plugin.event.events.PluginModuleUnavailableEvent;
32  import com.atlassian.plugin.event.impl.DefaultPluginEventManager;
33  import com.atlassian.plugin.event.listeners.FailListener;
34  import com.atlassian.plugin.event.listeners.PassListener;
35  import com.atlassian.plugin.factories.LegacyDynamicPluginFactory;
36  import com.atlassian.plugin.factories.PluginFactory;
37  import com.atlassian.plugin.factories.XmlDynamicPluginFactory;
38  import com.atlassian.plugin.hostcontainer.DefaultHostContainer;
39  import com.atlassian.plugin.impl.AbstractDelegatingPlugin;
40  import com.atlassian.plugin.impl.StaticPlugin;
41  import com.atlassian.plugin.impl.UnloadablePlugin;
42  import com.atlassian.plugin.loaders.DirectoryPluginLoader;
43  import com.atlassian.plugin.loaders.DynamicPluginLoader;
44  import com.atlassian.plugin.loaders.PluginLoader;
45  import com.atlassian.plugin.loaders.SinglePluginLoader;
46  import com.atlassian.plugin.loaders.classloading.DirectoryPluginLoaderUtils;
47  import com.atlassian.plugin.manager.store.MemoryPluginPersistentStateStore;
48  import com.atlassian.plugin.metadata.ClasspathFilePluginMetadata;
49  import com.atlassian.plugin.mock.MockAnimal;
50  import com.atlassian.plugin.mock.MockAnimalModuleDescriptor;
51  import com.atlassian.plugin.mock.MockBear;
52  import com.atlassian.plugin.mock.MockMineral;
53  import com.atlassian.plugin.mock.MockMineralModuleDescriptor;
54  import com.atlassian.plugin.mock.MockThing;
55  import com.atlassian.plugin.mock.MockVegetableModuleDescriptor;
56  import com.atlassian.plugin.mock.MockVegetableSubclassModuleDescriptor;
57  import com.atlassian.plugin.module.ModuleFactory;
58  import com.atlassian.plugin.parsers.DescriptorParser;
59  import com.atlassian.plugin.parsers.DescriptorParserFactory;
60  import com.atlassian.plugin.predicate.ModuleDescriptorPredicate;
61  import com.atlassian.plugin.predicate.PluginPredicate;
62  import com.atlassian.plugin.repositories.FilePluginInstaller;
63  import com.atlassian.plugin.test.PluginJarBuilder;
64  import com.google.common.collect.ImmutableList;
65  import com.google.common.collect.ImmutableSet;
66  import com.google.common.collect.Iterables;
67  import com.google.common.collect.Lists;
68  
69  import org.apache.commons.io.FileUtils;
70  import org.apache.commons.io.IOUtils;
71  import org.junit.After;
72  import org.junit.Before;
73  import org.junit.Test;
74  
75  import java.io.File;
76  import java.io.FileOutputStream;
77  import java.io.IOException;
78  import java.io.InputStream;
79  import java.net.URISyntaxException;
80  import java.net.URL;
81  import java.util.ArrayList;
82  import java.util.Arrays;
83  import java.util.Collection;
84  import java.util.Collections;
85  import java.util.List;
86  import java.util.concurrent.atomic.AtomicBoolean;
87  
88  import static com.atlassian.plugin.loaders.classloading.DirectoryPluginLoaderUtils.PADDINGTON_JAR;
89  import static com.google.common.collect.ImmutableList.copyOf;
90  import static java.util.Arrays.asList;
91  import static java.util.Collections.singleton;
92  import static org.junit.Assert.*;
93  import static org.mockito.Matchers.any;
94  import static org.mockito.Matchers.isA;
95  import static org.mockito.Mockito.doThrow;
96  import static org.mockito.Mockito.mock;
97  import static org.mockito.Mockito.never;
98  import static org.mockito.Mockito.times;
99  import static org.mockito.Mockito.verify;
100 import static org.mockito.Mockito.when;
101 import static org.hamcrest.Matchers.hasSize;
102 
103 public class TestDefaultPluginManager
104 {
105     /**
106      * the object being tested
107      */
108     protected DefaultPluginManager manager;
109 
110     private PluginPersistentStateStore pluginStateStore;
111     private DefaultModuleDescriptorFactory moduleDescriptorFactory; // we should be able to use the interface here?
112 
113     private DirectoryPluginLoader directoryPluginLoader;
114     protected PluginEventManager pluginEventManager;
115     private File pluginsDirectory;
116     private File pluginsTestDir;
117 
118     private void createFillAndCleanTempPluginDirectory() throws IOException
119     {
120         final DirectoryPluginLoaderUtils.ScannerDirectories directories = DirectoryPluginLoaderUtils.createFillAndCleanTempPluginDirectory();
121         pluginsDirectory = directories.pluginsDirectory;
122         pluginsTestDir = directories.pluginsTestDir;
123     }
124 
125     @Before
126     public void setUp() throws Exception
127     {
128         pluginEventManager = new DefaultPluginEventManager();
129 
130         pluginStateStore = new MemoryPluginPersistentStateStore();
131         moduleDescriptorFactory = new DefaultModuleDescriptorFactory(new DefaultHostContainer());
132     }
133 
134     @After
135     public void tearDown() throws Exception
136     {
137         manager = null;
138         moduleDescriptorFactory = null;
139         pluginStateStore = null;
140 
141         if (directoryPluginLoader != null)
142         {
143             directoryPluginLoader = null;
144         }
145     }
146 
147     protected DefaultPluginManager newDefaultPluginManager(PluginLoader... pluginLoaders)
148     {
149         manager = new DefaultPluginManager(pluginStateStore, copyOf(pluginLoaders), moduleDescriptorFactory, pluginEventManager, true);
150         return manager;
151     }
152 
153     protected PluginAccessor getPluginAccessor()
154     {
155         return manager;
156     }
157 
158     @Test
159     public void testRetrievePlugins() throws PluginParseException
160     {
161         manager = newDefaultPluginManager(
162                 new SinglePluginLoader("test-atlassian-plugin.xml"),
163                 new SinglePluginLoader("test-disabled-plugin.xml"));
164 
165         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
166         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
167         manager.init();
168 
169         assertThat(manager.getPlugins(), hasSize(2));
170         assertThat(manager.getEnabledPlugins(), hasSize(1));
171         manager.enablePlugin("test.disabled.plugin");
172         assertThat(manager.getEnabledPlugins(), hasSize(2));
173     }
174 
175     private enum FailureMode{
176         FAIL_TO_ENABLE,
177         FAIL_TO_DISABLE
178     }
179 
180     private ModuleDescriptor<Object> mockFailingModuleDescriptor(final String completeKey, final FailureMode... failureModes)
181     {
182         return new AbstractModuleDescriptor<Object>(ModuleFactory.LEGACY_MODULE_FACTORY)
183         {
184             @Override
185             public String getKey()
186             {
187                 return completeKey.substring(completeKey.lastIndexOf(":") + 1, completeKey.length());
188             }
189 
190             @Override
191             public String getCompleteKey()
192             {
193                 return completeKey;
194             }
195 
196             @Override
197             public void enabled()
198             {
199                 if (Lists.newArrayList(failureModes).contains(FailureMode.FAIL_TO_ENABLE))
200                     throw new IllegalArgumentException("Cannot enable");
201             }
202 
203             @Override
204             public void disabled()
205             {
206                 if (Lists.newArrayList(failureModes).contains(FailureMode.FAIL_TO_DISABLE))
207                     throw new IllegalArgumentException("Cannot disable");
208             }
209 
210             @Override
211             public Object getModule()
212             {
213                 return null;
214             }
215         };
216     }
217     
218     @Test
219     public void testEnableModuleFailed() throws PluginParseException
220     {
221         final PluginLoader mockPluginLoader = mock(PluginLoader.class);
222         final ModuleDescriptor<Object> badModuleDescriptor = mockFailingModuleDescriptor("foo:bar", FailureMode.FAIL_TO_ENABLE);
223 
224         final AbstractModuleDescriptor goodModuleDescriptor = mock(AbstractModuleDescriptor.class);
225         when(goodModuleDescriptor.getKey()).thenReturn("baz");
226         when(goodModuleDescriptor.getCompleteKey()).thenReturn("foo:baz");
227         when(goodModuleDescriptor.isEnabledByDefault()).thenReturn(true);
228 
229         Plugin plugin = mockStaticPlugin("foo", goodModuleDescriptor, badModuleDescriptor);
230         
231         when(mockPluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Collections.singletonList(plugin));
232 
233         pluginEventManager.register(new FailListener(PluginEnabledEvent.class));
234 
235         MyModuleDisabledListener listener = new MyModuleDisabledListener(goodModuleDescriptor);
236         pluginEventManager.register(listener);
237 
238         manager = newDefaultPluginManager(mockPluginLoader);
239         manager.init();
240 
241         assertThat(getPluginAccessor().getPlugins(), hasSize(1));
242         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(0));
243         plugin = getPluginAccessor().getPlugin("foo");
244         assertFalse(plugin.getPluginState() == PluginState.ENABLED);
245         assertTrue(plugin instanceof UnloadablePlugin);
246         assertTrue(listener.isCalled());
247     }
248 
249     public static class MyModuleDisabledListener
250     {
251         private final ModuleDescriptor goodModuleDescriptor;
252         private volatile boolean disableCalled = false;
253 
254         public MyModuleDisabledListener(ModuleDescriptor goodModuleDescriptor)
255         {
256             this.goodModuleDescriptor = goodModuleDescriptor;
257         }
258 
259         @PluginEventListener
260         public void onDisable(PluginModuleDisabledEvent evt)
261         {
262             if (evt.getModule().equals(goodModuleDescriptor))
263             {
264                 disableCalled = true;
265             }
266         }
267 
268         public boolean isCalled()
269         {
270             return disableCalled;
271         }
272     }
273 
274     @Test
275     public void testEnabledModuleOutOfSyncWithPlugin() throws PluginParseException
276     {
277         final PluginLoader mockPluginLoader = mock(PluginLoader.class);
278         Plugin plugin = new StaticPlugin();
279         plugin.setKey("foo");
280         plugin.setEnabledByDefault(true);
281         plugin.setPluginInformation(new PluginInformation());
282 
283         when(mockPluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Collections.singletonList(plugin));
284 
285         manager = newDefaultPluginManager(mockPluginLoader);
286         manager.init();
287 
288         assertThat(getPluginAccessor().getPlugins(), hasSize(1));
289         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
290         plugin = getPluginAccessor().getPlugin("foo");
291         assertTrue(plugin.getPluginState() == PluginState.ENABLED);
292         assertTrue(getPluginAccessor().isPluginEnabled("foo"));
293         plugin.disable();
294         assertFalse(plugin.getPluginState() == PluginState.ENABLED);
295         assertFalse(getPluginAccessor().isPluginEnabled("foo"));
296     }
297 
298     @Test
299     public void testDisablePluginModuleWithCannotDisableAnnotation()
300     {
301         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
302         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
303         moduleDescriptorFactory.addModuleDescriptor("bullshit", MockUnusedModuleDescriptor.class);
304         moduleDescriptorFactory.addModuleDescriptor("vegetable", MockVegetableModuleDescriptor.class);
305 
306         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
307         manager.init();
308 
309         final String pluginKey = "test.atlassian.plugin";
310         final String disablableModuleKey = pluginKey + ":bear";
311         final String moduleKey = pluginKey + ":veg";
312 
313         // First, make sure we can disable the bear module
314         manager.disablePluginModule(disablableModuleKey);
315         assertNull(getPluginAccessor().getEnabledPluginModule(disablableModuleKey));
316 
317         // Now, make sure we can't disable the veg module
318         manager.disablePluginModule(moduleKey);
319         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
320     }
321 
322     @Test
323     public void testDisablePluginModuleWithCannotDisableAnnotationInSuperclass()
324     {
325         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
326         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
327         moduleDescriptorFactory.addModuleDescriptor("bullshit", MockUnusedModuleDescriptor.class);
328         moduleDescriptorFactory.addModuleDescriptor("vegetable", MockVegetableModuleDescriptor.class);
329         moduleDescriptorFactory.addModuleDescriptor("vegetableSubclass", MockVegetableSubclassModuleDescriptor.class);
330 
331         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
332         manager.init();
333 
334         final String pluginKey = "test.atlassian.plugin";
335         final String disablableModuleKey = pluginKey + ":bear";
336         final String moduleKey = pluginKey + ":vegSubclass";
337 
338         // First, make sure we can disable the bear module
339         manager.disablePluginModule(disablableModuleKey);
340         assertNull(getPluginAccessor().getEnabledPluginModule(disablableModuleKey));
341 
342         // Now, make sure we can't disable the vegSubclass module
343         manager.disablePluginModule(moduleKey);
344         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
345     }
346 
347     @Test
348     public void testEnabledDisabledRetrieval() throws PluginParseException
349     {
350         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
351         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
352         moduleDescriptorFactory.addModuleDescriptor("bullshit", MockUnusedModuleDescriptor.class);
353         moduleDescriptorFactory.addModuleDescriptor("vegetable", MockVegetableModuleDescriptor.class);
354 
355         final PassListener enabledListener = new PassListener(PluginEnabledEvent.class);
356         final PassListener disabledListener = new PassListener(PluginDisabledEvent.class);
357         pluginEventManager.register(enabledListener);
358         pluginEventManager.register(disabledListener);
359 
360         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
361         manager.init();
362 
363         // check non existent plugins don't show
364         assertNull(getPluginAccessor().getPlugin("bull:shit"));
365         assertNull(getPluginAccessor().getEnabledPlugin("bull:shit"));
366         assertNull(getPluginAccessor().getPluginModule("bull:shit"));
367         assertNull(getPluginAccessor().getEnabledPluginModule("bull:shit"));
368         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByClass(NothingModuleDescriptor.class).isEmpty());
369         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByType("bullshit").isEmpty());
370 
371         final String pluginKey = "test.atlassian.plugin";
372         final String moduleKey = pluginKey + ":bear";
373 
374         // retrieve everything when enabled
375         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
376         assertNotNull(getPluginAccessor().getEnabledPlugin(pluginKey));
377         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
378         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
379         assertNull(getPluginAccessor().getEnabledPluginModule(pluginKey + ":shit"));
380         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
381         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByType("animal").isEmpty());
382         assertFalse(getPluginAccessor().getEnabledModulesByClass(MockBear.class).isEmpty());
383         assertEquals(new MockBear(), getPluginAccessor().getEnabledModulesByClass(MockBear.class).get(0));
384         enabledListener.assertCalled();
385 
386         // now only retrieve via always retrieve methods
387         manager.disablePlugin(pluginKey);
388         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
389         assertNull(getPluginAccessor().getEnabledPlugin(pluginKey));
390         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
391         assertNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
392         assertTrue(getPluginAccessor().getEnabledModulesByClass(com.atlassian.plugin.mock.MockBear.class).isEmpty());
393         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
394         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByType("animal").isEmpty());
395         disabledListener.assertCalled();
396 
397         // now enable again and check back to start
398         manager.enablePlugin(pluginKey);
399         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
400         assertNotNull(getPluginAccessor().getEnabledPlugin(pluginKey));
401         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
402         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
403         assertFalse(getPluginAccessor().getEnabledModulesByClass(com.atlassian.plugin.mock.MockBear.class).isEmpty());
404         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
405         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByType("animal").isEmpty());
406         enabledListener.assertCalled();
407 
408         // now let's disable the module, but not the plugin
409         pluginEventManager.register(new FailListener(PluginEnabledEvent.class));
410         manager.disablePluginModule(moduleKey);
411         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
412         assertNotNull(getPluginAccessor().getEnabledPlugin(pluginKey));
413         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
414         assertNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
415         assertTrue(getPluginAccessor().getEnabledModulesByClass(com.atlassian.plugin.mock.MockBear.class).isEmpty());
416         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
417         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByType("animal").isEmpty());
418 
419         // now enable the module again
420         pluginEventManager.register(new FailListener(PluginDisabledEvent.class));
421         manager.enablePluginModule(moduleKey);
422         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
423         assertNotNull(getPluginAccessor().getEnabledPlugin(pluginKey));
424         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
425         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
426         assertFalse(getPluginAccessor().getEnabledModulesByClass(com.atlassian.plugin.mock.MockBear.class).isEmpty());
427         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
428         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByType("animal").isEmpty());
429     }
430 
431     @Test
432     public void testDuplicatePluginKeysAreIgnored() throws PluginParseException
433     {
434         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
435 
436         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"), new SinglePluginLoader("test-atlassian-plugin.xml"));
437         manager.init();
438         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
439     }
440 
441     @Test
442     public void testDuplicateSnapshotVersionsAreNotLoaded() throws PluginParseException
443     {
444         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
445 
446         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-snapshot-plugin.xml"), new SinglePluginLoader("test-atlassian-snapshot-plugin.xml"));
447         manager.init();
448         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
449     }
450 
451     @Test
452     public void testChangedSnapshotVersionIsLoaded() throws PluginParseException
453     {
454         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
455 
456         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-snapshot-plugin.xml"), new SinglePluginLoader("test-atlassian-snapshot-plugin-changed-same-version.xml"));
457         manager.init();
458         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
459         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
460         assertEquals("1.1-SNAPSHOT", plugin.getPluginInformation().getVersion());
461         assertEquals("This plugin descriptor has been changed!", plugin.getPluginInformation().getDescription());
462     }
463 
464     @Test
465     public void testLoadOlderDuplicatePlugin()
466     {
467         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
468         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
469 
470         manager = newDefaultPluginManager(new MultiplePluginLoader("test-atlassian-plugin-newer.xml"), new MultiplePluginLoader("test-atlassian-plugin.xml", "test-another-plugin.xml"));
471         manager.init();
472         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(2));
473     }
474 
475     @Test
476     public void testLoadOlderDuplicatePluginDoesNotTryToEnableIt()
477     {
478         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
479         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
480 
481         final Plugin plugin = new StaticPlugin()
482         {
483             @Override
484             protected PluginState enableInternal()
485             {
486                 fail("enable() must never be called on a earlier version of plugin when later version is installed");
487                 return null;
488             }
489 
490             @Override
491             public void disableInternal()
492             {
493                 fail("disable() must never be called on a earlier version of plugin when later version is installed");
494             }
495         };
496         plugin.setKey("test.atlassian.plugin");
497         plugin.getPluginInformation().setVersion("1.0");
498 
499         manager = newDefaultPluginManager(new MultiplePluginLoader("test-atlassian-plugin-newer.xml"));
500         manager.init();
501         manager.addPlugins(null, Collections.singletonList(plugin));
502     }
503 
504     @Test
505     public void testLoadNewerDuplicatePlugin()
506     {
507         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
508         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
509 
510         manager = newDefaultPluginManager(
511                 new SinglePluginLoader("test-atlassian-plugin.xml"),
512                 new SinglePluginLoader("test-atlassian-plugin-newer.xml"));
513         manager.init();
514         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
515         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
516         assertEquals("1.1", plugin.getPluginInformation().getVersion());
517     }
518 
519     @Test
520     public void testLoadNewerDuplicateDynamicPluginPreservesPluginState() throws PluginParseException
521     {
522         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
523         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
524 
525         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin.xml"));
526         manager.init();
527 
528         pluginStateStore.save(PluginPersistentState.Builder.create(pluginStateStore.load()).setEnabled(manager.getPlugin("test.atlassian.plugin"),
529                 false).toState());
530 
531         assertFalse(getPluginAccessor().isPluginEnabled("test.atlassian.plugin"));
532         manager.shutdown();
533 
534         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin-newer.xml"));
535         manager.init();
536 
537         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
538         assertEquals("1.1", plugin.getPluginInformation().getVersion());
539         assertFalse(getPluginAccessor().isPluginEnabled("test.atlassian.plugin"));
540     }
541 
542     @Test
543     public void testLoadNewerDuplicateDynamicPluginPreservesModuleState() throws PluginParseException
544     {
545         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
546         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
547 
548         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin.xml"));
549         manager.init();
550 
551         pluginStateStore.save(PluginPersistentState.Builder.create(pluginStateStore.load()).setEnabled(
552                 getPluginAccessor().getPluginModule("test.atlassian.plugin:bear"), false).toState());
553 
554         manager.shutdown();
555 
556         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin-newer.xml"));
557         manager.init();
558 
559         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
560         assertEquals("1.1", plugin.getPluginInformation().getVersion());
561         assertFalse(getPluginAccessor().isPluginModuleEnabled("test.atlassian.plugin:bear"));
562         assertTrue(getPluginAccessor().isPluginModuleEnabled("test.atlassian.plugin:gold"));
563     }
564 
565     @Test
566     public void testLoadChangedDynamicPluginWithSameVersionNumberDoesNotReplaceExisting() throws PluginParseException
567     {
568         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
569         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
570 
571         manager = newDefaultPluginManager(
572                 new SinglePluginLoaderWithRemoval("test-atlassian-plugin.xml"),
573                 new SinglePluginLoaderWithRemoval("test-atlassian-plugin-changed-same-version.xml"));
574         manager.init();
575 
576         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
577         assertEquals("Test Plugin", plugin.getName());
578     }
579 
580     @Test
581     public void testGetPluginsWithPluginMatchingPluginPredicate() throws Exception
582     {
583         final Plugin plugin = mockTestPlugin(Collections.emptyList());
584 
585         final PluginPredicate mockPluginPredicate = mock(PluginPredicate.class);
586         when(mockPluginPredicate.matches(plugin)).thenReturn(true);
587 
588         manager = newDefaultPluginManager();
589         manager.addPlugins(null, Collections.singletonList(plugin));
590         final Collection<Plugin> plugins = getPluginAccessor().getPlugins(mockPluginPredicate);
591 
592         assertThat(plugins, hasSize(1));
593         assertTrue(plugins.contains(plugin));
594         verify(mockPluginPredicate).matches(any(Plugin.class));
595     }
596 
597     @Test
598     public void testGetPluginsWithPluginNotMatchingPluginPredicate() throws Exception
599     {
600         final Plugin plugin = mockTestPlugin(Collections.emptyList());
601 
602         final PluginPredicate mockPluginPredicate = mock(PluginPredicate.class);
603 
604         manager = newDefaultPluginManager();
605         manager.addPlugins(null, Collections.singletonList(plugin));
606         final Collection<Plugin> plugins = getPluginAccessor().getPlugins(mockPluginPredicate);
607 
608         assertThat(plugins, hasSize(0));
609     }
610 
611     @Test
612     public void testGetPluginModulesWithModuleMatchingPredicate() throws Exception
613     {
614         final MockThing module = new MockThing()
615         {
616         };
617         @SuppressWarnings("unchecked")
618         final ModuleDescriptor<MockThing> moduleDescriptor = mock(ModuleDescriptor.class);
619         when(moduleDescriptor.getModule()).thenReturn(module);
620         when(moduleDescriptor.getCompleteKey()).thenReturn("some-plugin-key:module");
621         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
622 
623         final Plugin plugin = mockTestPlugin(Collections.singleton(moduleDescriptor));
624         when(plugin.getModuleDescriptor("module")).thenReturn((ModuleDescriptor) moduleDescriptor);
625 
626         final ModuleDescriptorPredicate mockModulePredicate = mock(ModuleDescriptorPredicate.class);
627         when(mockModulePredicate.matches(moduleDescriptor)).thenReturn(true);
628 
629         manager = newDefaultPluginManager();
630         manager.addPlugins(null, Collections.singletonList(plugin));
631         @SuppressWarnings("unchecked")
632         final ModuleDescriptorPredicate<MockThing> predicate = (ModuleDescriptorPredicate<MockThing>) mockModulePredicate;
633         final Collection<MockThing> modules = getPluginAccessor().getModules(predicate);
634 
635         assertThat(modules, hasSize(1));
636         assertTrue(modules.contains(module));
637 
638         verify(mockModulePredicate).matches(moduleDescriptor);
639     }
640 
641     @Test
642     public void testGetPluginModulesWithGetModuleThrowingException() throws Exception
643     {
644         final Plugin badPlugin = new StaticPlugin();
645         badPlugin.setKey("bad");
646         final MockModuleDescriptor<Object> badDescriptor = new MockModuleDescriptor<Object>(badPlugin, "bad", new Object())
647         {
648             @Override
649             public Object getModule()
650             {
651                 throw new RuntimeException();
652             }
653         };
654         badPlugin.addModuleDescriptor(badDescriptor);
655 
656         final Plugin goodPlugin = new StaticPlugin();
657         goodPlugin.setKey("good");
658         final MockModuleDescriptor<Object> goodDescriptor = new MockModuleDescriptor<Object>(goodPlugin, "good", new Object());
659         goodPlugin.addModuleDescriptor(goodDescriptor);
660 
661         manager = newDefaultPluginManager();
662         manager.addPlugins(null, Arrays.asList(goodPlugin, badPlugin));
663         manager.enablePlugin("bad");
664         manager.enablePlugin("good");
665 
666         assertTrue(getPluginAccessor().isPluginEnabled("bad"));
667         assertTrue(getPluginAccessor().isPluginEnabled("good"));
668         final Collection<Object> modules = getPluginAccessor().getEnabledModulesByClass(Object.class);
669 
670         assertThat(modules, hasSize(1));
671         assertFalse(getPluginAccessor().isPluginEnabled("bad"));
672         assertTrue(getPluginAccessor().isPluginEnabled("good"));
673     }
674 
675     @Test
676     public void testGetPluginModulesWith2GetModulesThrowingExceptionOnlyNotifiesOnce() throws Exception
677     {
678         final Plugin badPlugin = new StaticPlugin();
679         badPlugin.setKey("bad");
680         final MockModuleDescriptor<Object> badDescriptor = new MockModuleDescriptor<Object>(badPlugin, "bad", new Object())
681         {
682             @Override
683             public Object getModule()
684             {
685                 throw new RuntimeException();
686             }
687         };
688         badPlugin.addModuleDescriptor(badDescriptor);
689         final MockModuleDescriptor<Object> badDescriptor2 = new MockModuleDescriptor<Object>(badPlugin, "bad2", new Object())
690         {
691             @Override
692             public Object getModule()
693             {
694                 throw new RuntimeException();
695             }
696         };
697         badPlugin.addModuleDescriptor(badDescriptor2);
698 
699         final Plugin goodPlugin = new StaticPlugin();
700         goodPlugin.setKey("good");
701         final MockModuleDescriptor<Object> goodDescriptor = new MockModuleDescriptor<Object>(goodPlugin, "good", new Object());
702         goodPlugin.addModuleDescriptor(goodDescriptor);
703         DisabledPluginCounter counter = new DisabledPluginCounter();
704         pluginEventManager.register(counter);
705 
706         manager = newDefaultPluginManager();
707         manager.addPlugins(null, Arrays.asList(goodPlugin, badPlugin));
708         manager.enablePlugin("bad");
709         manager.enablePlugin("good");
710 
711         assertTrue(getPluginAccessor().isPluginEnabled("bad"));
712         assertTrue(getPluginAccessor().isPluginEnabled("good"));
713         final Collection<Object> modules = getPluginAccessor().getEnabledModulesByClass(Object.class);
714 
715         assertThat(modules, hasSize(1));
716         assertFalse(getPluginAccessor().isPluginEnabled("bad"));
717         assertTrue(getPluginAccessor().isPluginEnabled("good"));
718         assertEquals(1, counter.disableCount);
719     }
720 
721     public static class DisabledPluginCounter
722     {
723         int disableCount = 0;
724         @PluginEventListener
725         public void consume(PluginDisabledEvent element)
726         {
727             disableCount++;
728         }
729     }
730 
731     @Test
732     public void testGetPluginModulesWithModuleNotMatchingPredicate() throws Exception
733     {
734         @SuppressWarnings("unchecked")
735         final ModuleDescriptor<MockThing> moduleDescriptor = mock(ModuleDescriptor.class);
736         when(moduleDescriptor.getCompleteKey()).thenReturn("some-plugin-key:module");
737         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
738 
739         final Plugin plugin = mockTestPlugin(Collections.singleton(moduleDescriptor));
740         when(plugin.getModuleDescriptor("module")).thenReturn((ModuleDescriptor) moduleDescriptor);
741 
742         @SuppressWarnings("unchecked")
743         final ModuleDescriptorPredicate<MockThing> predicate = mock(ModuleDescriptorPredicate.class);
744 
745         manager = newDefaultPluginManager();
746         manager.addPlugins(null, Collections.singletonList(plugin));
747         final Collection<MockThing> modules = getPluginAccessor().getModules(predicate);
748 
749         assertThat(modules, hasSize(0));
750 
751         verify(predicate).matches(moduleDescriptor);
752     }
753 
754     @Test
755     public void testGetPluginModuleDescriptorWithModuleMatchingPredicate() throws Exception
756     {
757         @SuppressWarnings("unchecked")
758         final ModuleDescriptor<MockThing> moduleDescriptor = mock(ModuleDescriptor.class);
759         when(moduleDescriptor.getCompleteKey()).thenReturn("some-plugin-key:module");
760         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
761 
762         final Plugin plugin = mockTestPlugin(Collections.singleton(moduleDescriptor));
763         when(plugin.getModuleDescriptor("module")).thenReturn((ModuleDescriptor) moduleDescriptor);
764 
765         @SuppressWarnings("unchecked")
766         final ModuleDescriptorPredicate<MockThing> predicate = mock(ModuleDescriptorPredicate.class);
767         when(predicate.matches(moduleDescriptor)).thenReturn(true);
768 
769         manager = newDefaultPluginManager();
770         manager.addPlugins(null, Collections.singletonList(plugin));
771         final Collection<ModuleDescriptor<MockThing>> modules = getPluginAccessor().getModuleDescriptors(predicate);
772 
773         assertThat(modules, hasSize(1));
774         assertTrue(modules.contains(moduleDescriptor));
775 
776         verify(predicate).matches(moduleDescriptor);
777     }
778 
779     @Test
780     public void testGetPluginModuleDescriptorsWithModuleNotMatchingPredicate() throws Exception
781     {
782         @SuppressWarnings("unchecked")
783         final ModuleDescriptor<MockThing> moduleDescriptor = mock(ModuleDescriptor.class);
784         when(moduleDescriptor.getCompleteKey()).thenReturn("some-plugin-key:module");
785         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
786 
787         final Plugin plugin = mockTestPlugin(Collections.singleton(moduleDescriptor));
788         when(plugin.getModuleDescriptor("module")).thenReturn((ModuleDescriptor) moduleDescriptor);
789 
790         @SuppressWarnings("unchecked")
791         final ModuleDescriptorPredicate<MockThing> predicate = mock(ModuleDescriptorPredicate.class);
792 
793         manager = newDefaultPluginManager();
794         manager.addPlugins(null, Collections.singletonList(plugin));
795         final Collection<MockThing> modules = getPluginAccessor().getModules(predicate);
796 
797         assertThat(modules, hasSize(0));
798 
799         verify(predicate).matches(moduleDescriptor);
800     }
801 
802     private Plugin mockTestPlugin(Collection moduleDescriptors)
803     {
804         final Plugin mockPlugin = mock(Plugin.class);
805         when(mockPlugin.getKey()).thenReturn("some-plugin-key");
806         when(mockPlugin.isEnabledByDefault()).thenReturn(true);
807         when(mockPlugin.isEnabled()).thenReturn(true);
808         when(mockPlugin.getPluginState()).thenReturn(PluginState.ENABLED);
809         when(mockPlugin.getModuleDescriptors()).thenReturn(moduleDescriptors);
810         return mockPlugin;
811     }
812 
813     @Test
814     public void testGetPluginAndModules() throws PluginParseException
815     {
816         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
817         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
818 
819         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
820         manager.init();
821 
822         final Plugin plugin = manager.getPlugin("test.atlassian.plugin");
823         assertNotNull(plugin);
824         assertEquals("Test Plugin", plugin.getName());
825 
826         final ModuleDescriptor<?> bear = plugin.getModuleDescriptor("bear");
827         assertEquals(bear, getPluginAccessor().getPluginModule("test.atlassian.plugin:bear"));
828     }
829 
830     @Test
831     public void testGetModuleByModuleClassOneFound() throws PluginParseException
832     {
833         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
834         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
835 
836         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
837         manager.init();
838 
839         final List<MockAnimalModuleDescriptor> animalDescriptors = getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class);
840         assertNotNull(animalDescriptors);
841         assertThat(animalDescriptors, hasSize(1));
842         final ModuleDescriptor<MockAnimal> moduleDescriptor = animalDescriptors.iterator().next();
843         assertEquals("Bear Animal", moduleDescriptor.getName());
844 
845         final List<MockMineralModuleDescriptor> mineralDescriptors = getPluginAccessor().getEnabledModuleDescriptorsByClass(MockMineralModuleDescriptor.class);
846         assertNotNull(mineralDescriptors);
847         assertThat(mineralDescriptors, hasSize(1));
848         final ModuleDescriptor<MockMineral> mineralDescriptor = mineralDescriptors.iterator().next();
849         assertEquals("Bar", mineralDescriptor.getName());
850     }
851 
852     @Test
853     public void testGetModuleByModuleClassAndDescriptor() throws PluginParseException
854     {
855         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
856         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
857 
858         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
859         manager.init();
860 
861         final Collection<MockBear> bearModules = getPluginAccessor().getEnabledModulesByClassAndDescriptor(
862                 new Class[]{MockAnimalModuleDescriptor.class, MockMineralModuleDescriptor.class}, MockBear.class);
863         assertNotNull(bearModules);
864         assertThat(bearModules, hasSize(1));
865         assertTrue(bearModules.iterator().next() instanceof MockBear);
866 
867         final Collection<MockBear> noModules = getPluginAccessor().getEnabledModulesByClassAndDescriptor(new Class[]{}, MockBear.class);
868         assertNotNull(noModules);
869         assertThat(noModules, hasSize(0));
870 
871         final Collection<MockThing> mockThings = getPluginAccessor().getEnabledModulesByClassAndDescriptor(
872                 new Class[]{MockAnimalModuleDescriptor.class, MockMineralModuleDescriptor.class}, MockThing.class);
873         assertNotNull(mockThings);
874         assertThat(mockThings, hasSize(2));
875         assertTrue(mockThings.iterator().next() instanceof MockThing);
876         assertTrue(mockThings.iterator().next() instanceof MockThing);
877 
878         final Collection<MockThing> mockThingsFromMineral = getPluginAccessor().getEnabledModulesByClassAndDescriptor(
879                 new Class[]{MockMineralModuleDescriptor.class}, MockThing.class);
880         assertNotNull(mockThingsFromMineral);
881         assertThat(mockThingsFromMineral, hasSize(1));
882         final Object o = mockThingsFromMineral.iterator().next();
883         assertTrue(o instanceof MockMineral);
884     }
885 
886     @Test
887     public void testGetModuleByModuleClassNoneFound() throws PluginParseException
888     {
889         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
890         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
891 
892         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
893         manager.init();
894 
895         class MockSilver implements MockMineral
896         {
897             public int getWeight()
898             {
899                 return 3;
900             }
901         }
902 
903         final Collection<MockSilver> descriptors = getPluginAccessor().getEnabledModulesByClass(MockSilver.class);
904         assertNotNull(descriptors);
905         assertTrue(descriptors.isEmpty());
906     }
907 
908     @Test
909     public void testGetModuleDescriptorsByType() throws PluginParseException
910     {
911         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
912         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
913 
914         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
915         manager.init();
916 
917         Collection<ModuleDescriptor<MockThing>> descriptors = getPluginAccessor().getEnabledModuleDescriptorsByType("animal");
918         assertNotNull(descriptors);
919         assertThat(descriptors, hasSize(1));
920         ModuleDescriptor<MockThing> moduleDescriptor = descriptors.iterator().next();
921         assertEquals("Bear Animal", moduleDescriptor.getName());
922 
923         descriptors = getPluginAccessor().getEnabledModuleDescriptorsByType("mineral");
924         assertNotNull(descriptors);
925         assertThat(descriptors, hasSize(1));
926         moduleDescriptor = descriptors.iterator().next();
927         assertEquals("Bar", moduleDescriptor.getName());
928 
929         try
930         {
931             getPluginAccessor().getEnabledModuleDescriptorsByType("foobar");
932         }
933         catch (final IllegalArgumentException e)
934         {
935             fail("Shouldn't have thrown exception.");
936         }
937     }
938 
939     @Test
940     public void testRetrievingDynamicResources() throws PluginParseException, IOException
941     {
942         createFillAndCleanTempPluginDirectory();
943 
944         final DefaultPluginManager manager = makeClassLoadingPluginManager();
945 
946         final InputStream is = manager.getPluginResourceAsStream("test.atlassian.plugin.classloaded", "atlassian-plugin.xml");
947         assertNotNull(is);
948         IOUtils.closeQuietly(is);
949     }
950 
951     @Test
952     public void testGetDynamicPluginClass() throws IOException, PluginParseException
953     {
954         createFillAndCleanTempPluginDirectory();
955 
956         final DefaultPluginManager manager = makeClassLoadingPluginManager();
957         try
958         {
959             manager.getDynamicPluginClass("com.atlassian.plugin.mock.MockPooh");
960         }
961         catch (final ClassNotFoundException e)
962         {
963             fail(e.getMessage());
964         }
965     }
966 
967     @Test
968     public void testGetEnabledPluginsDoesNotReturnEnablingPlugins() throws Exception
969     {
970         final PluginLoader mockPluginLoader = mock(PluginLoader.class);
971 
972         final Plugin firstPlugin = new StaticPlugin();
973         firstPlugin.setKey("first");
974         firstPlugin.setEnabledByDefault(false);
975         firstPlugin.setPluginInformation(new PluginInformation());
976 
977         manager = newDefaultPluginManager();
978         manager.enablePluginState(firstPlugin, pluginStateStore);
979 
980         final Plugin secondPlugin = new StaticPlugin()
981         {
982             public PluginState enableInternal()
983             {
984                 try
985                 {
986                     // Assert here when the first plugin has been started but this plugin still has not been loaded
987                     assertThat(manager.getPlugins(), hasSize(2));
988                     assertThat("First plugin should not be enabled", manager.getEnabledPlugins(), hasSize(0));
989                 }
990                 catch (Exception e)
991                 {
992                     throw new RuntimeException(e);
993                 }
994                 return PluginState.ENABLED;
995             }
996 
997             public void disableInternal()
998             {
999                 // do nothing
1000             }
1001         };
1002         secondPlugin.setKey("second");
1003         secondPlugin.setEnabledByDefault(false);
1004         secondPlugin.setPluginInformation(new PluginInformation());
1005         manager.enablePluginState(secondPlugin, pluginStateStore);
1006 
1007         when(mockPluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(firstPlugin, secondPlugin));
1008 
1009         manager = newDefaultPluginManager(mockPluginLoader);
1010         manager.init();
1011     }
1012 
1013     @Test
1014     public void testFindingNewPlugins() throws PluginParseException, IOException
1015     {
1016         createFillAndCleanTempPluginDirectory();
1017 
1018         //delete paddington for the timebeing
1019         final File paddington = new File(pluginsTestDir, PADDINGTON_JAR);
1020         paddington.delete();
1021 
1022         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1023 
1024         assertThat(manager.getPlugins(), hasSize(1));
1025         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1026 
1027         //restore paddington to test plugins dir
1028         FileUtils.copyDirectory(pluginsDirectory, pluginsTestDir);
1029 
1030         manager.scanForNewPlugins();
1031         assertThat(manager.getPlugins(), hasSize(2));
1032         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1033         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded"));
1034 
1035         manager.scanForNewPlugins();
1036         assertThat(manager.getPlugins(), hasSize(2));
1037         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1038         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded"));
1039     }
1040 
1041     @Test
1042     public void testFindingNewPluginsNotLoadingRestartRequiredDescriptors() throws PluginParseException, IOException
1043     {
1044         createFillAndCleanTempPluginDirectory();
1045 
1046         final DynamicSinglePluginLoader dynamicSinglePluginLoader = new DynamicSinglePluginLoader("test.atlassian.plugin", "test-requiresRestart-plugin.xml");
1047         manager = makeClassLoadingPluginManager(dynamicSinglePluginLoader);
1048 
1049         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1050 
1051         assertThat(manager.getPlugins(), hasSize(2));
1052         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1053 
1054         // enable the dynamic plugin loader
1055         dynamicSinglePluginLoader.canLoad.set(true);
1056 
1057         manager.scanForNewPlugins();
1058         assertThat(manager.getPlugins(), hasSize(3));
1059         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1060         assertNotNull(manager.getPlugin("test.atlassian.plugin"));
1061 
1062         final Plugin plugin = manager.getPlugin("test.atlassian.plugin");
1063         assertTrue(plugin instanceof UnloadablePlugin);
1064         assertTrue(((UnloadablePlugin)plugin).getErrorText().contains("foo"));
1065 
1066         assertEquals(PluginRestartState.INSTALL, manager.getPluginRestartState("test.atlassian.plugin"));
1067     }
1068 
1069     /**
1070      * Tests upgrade of plugin where the old version didn't have any restart required module descriptors, but the new one does
1071      */
1072     @Test
1073     public void testFindingUpgradePluginsNotLoadingRestartRequiredDescriptors() throws PluginParseException, IOException
1074     {
1075         createFillAndCleanTempPluginDirectory();
1076 
1077         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1078 
1079         final DynamicSinglePluginLoader dynamicSinglePluginLoader = new DynamicSinglePluginLoader("test.atlassian.plugin.classloaded2", "test-requiresRestartWithUpgrade-plugin.xml");
1080         manager = makeClassLoadingPluginManager(dynamicSinglePluginLoader);
1081 
1082         assertThat(manager.getPlugins(), hasSize(2));
1083         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1084         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1085 
1086 
1087         dynamicSinglePluginLoader.canLoad.set(true);
1088 
1089         manager.scanForNewPlugins();
1090         assertThat(manager.getPlugins(), hasSize(2));
1091         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1092         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.atlassian.plugin.classloaded2"));
1093         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1094     }
1095 
1096     @Test
1097     public void testInstallPluginThatRequiresRestart() throws PluginParseException, IOException, InterruptedException
1098     {
1099         createFillAndCleanTempPluginDirectory();
1100         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1101         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1102         assertThat(manager.getPlugins(), hasSize(2));
1103 
1104         new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1105                 "<atlassian-plugin name='Test 2' i18n-name-key='test.name' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1106                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1107         manager.scanForNewPlugins();
1108 
1109         assertThat(manager.getPlugins(), hasSize(3));
1110         Plugin plugin = manager.getPlugin("test.restartrequired");
1111         assertNotNull(plugin);
1112         assertEquals("Test 2", plugin.getName());
1113         assertEquals("test.name", plugin.getI18nNameKey());
1114         assertEquals(1, plugin.getPluginsVersion());
1115         assertEquals("1.0", plugin.getPluginInformation().getVersion());
1116         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1117         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1118         assertEquals(PluginRestartState.INSTALL, manager.getPluginRestartState("test.restartrequired"));
1119 
1120         manager.shutdown();
1121         manager.init();
1122 
1123         assertThat(manager.getPlugins(), hasSize(3));
1124         assertNotNull(plugin);
1125         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1126         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1127         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1128     }
1129 
1130     @Test
1131     public void testInstallPluginThatRequiresRestartThenRevert() throws PluginParseException, IOException, InterruptedException
1132     {
1133         createFillAndCleanTempPluginDirectory();
1134         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1135         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1136         manager.setPluginInstaller(new FilePluginInstaller(pluginsTestDir));
1137         assertThat(manager.getPlugins(), hasSize(2));
1138 
1139         File pluginJar = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1140                 "<atlassian-plugin name='Test 2' i18n-name-key='test.name' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1141                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build();
1142         manager.installPlugin(new JarPluginArtifact(pluginJar));
1143 
1144         assertThat(manager.getPlugins(), hasSize(3));
1145         Plugin plugin = manager.getPlugin("test.restartrequired");
1146         assertNotNull(plugin);
1147         assertEquals("Test 2", plugin.getName());
1148         assertEquals("test.name", plugin.getI18nNameKey());
1149         assertEquals(1, plugin.getPluginsVersion());
1150         assertEquals("1.0", plugin.getPluginInformation().getVersion());
1151         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1152         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1153         assertEquals(PluginRestartState.INSTALL, manager.getPluginRestartState("test.restartrequired"));
1154 
1155         manager.revertRestartRequiredChange("test.restartrequired");
1156         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1157 
1158         manager.shutdown();
1159         manager.init();
1160 
1161         assertThat(manager.getPlugins(), hasSize(2));
1162         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1163         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1164     }
1165 
1166     @Test
1167     public void testUpgradePluginThatRequiresRestart() throws PluginParseException, IOException, InterruptedException
1168     {
1169         createFillAndCleanTempPluginDirectory();
1170         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1171 
1172         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1173                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1174                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1175 
1176         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1177 
1178         assertThat(manager.getPlugins(), hasSize(3));
1179         assertNotNull(manager.getPlugin("test.restartrequired"));
1180         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1181         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1182         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1183 
1184         // Some filesystems only record last modified in seconds
1185         Thread.sleep(1000);
1186         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1187                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1188                 "    </plugin-info>", "    <requiresRestart key='foo' />", "    <requiresRestart key='bar' />", "</atlassian-plugin>").build();
1189 
1190         origFile.delete();
1191         FileUtils.moveFile(updateFile, origFile);
1192 
1193         manager.scanForNewPlugins();
1194         assertThat(manager.getPlugins(), hasSize(3));
1195         assertNotNull(manager.getPlugin("test.restartrequired"));
1196         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1197         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1198         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1199 
1200         manager.shutdown();
1201         manager.init();
1202 
1203         assertThat(manager.getPlugins(), hasSize(3));
1204         assertNotNull(manager.getPlugin("test.restartrequired"));
1205         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1206         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(2));
1207         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1208     }
1209 
1210     @Test
1211     public void testUpgradePluginThatRequiresRestartThenReverted() throws PluginParseException, IOException, InterruptedException
1212     {
1213         createFillAndCleanTempPluginDirectory();
1214         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1215 
1216         new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1217                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>",
1218                 "    <plugin-info>",
1219                 "        <version>1.0</version>",
1220                 "    </plugin-info>",
1221                 "    <requiresRestart key='foo' />",
1222                 "</atlassian-plugin>")
1223                 .build(pluginsTestDir);
1224 
1225         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1226         manager.setPluginInstaller(new FilePluginInstaller(pluginsTestDir));
1227 
1228         assertThat(manager.getPlugins(), hasSize(3));
1229         assertNotNull(manager.getPlugin("test.restartrequired"));
1230         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1231         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1232         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1233 
1234         // Some filesystems only record last modified in seconds
1235         Thread.sleep(1000);
1236         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1237                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1238                 "    </plugin-info>", "    <requiresRestart key='foo' />", "    <requiresRestart key='bar' />", "</atlassian-plugin>").build();
1239 
1240         manager.installPlugin(new JarPluginArtifact(updateFile));
1241 
1242         assertThat(manager.getPlugins(), hasSize(3));
1243         assertNotNull(manager.getPlugin("test.restartrequired"));
1244         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1245         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1246         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1247 
1248         manager.revertRestartRequiredChange("test.restartrequired");
1249         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1250 
1251         manager.shutdown();
1252         manager.init();
1253 
1254         assertThat(manager.getPlugins(), hasSize(3));
1255         assertNotNull(manager.getPlugin("test.restartrequired"));
1256         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1257         assertEquals("1.0", manager.getPlugin("test.restartrequired").getPluginInformation().getVersion());
1258         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1259         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1260     }
1261 
1262     @Test
1263     public void testUpgradePluginThatRequiresRestartThenRevertedRevertsToOriginalPlugin() throws PluginParseException, IOException, InterruptedException
1264     {
1265         createFillAndCleanTempPluginDirectory();
1266         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1267 
1268         // Add the plugin to the plugins test directory so it is included when we first start up
1269         new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1270                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1271                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1272 
1273         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1274         manager.setPluginInstaller(new FilePluginInstaller(pluginsTestDir));
1275 
1276         assertThat(manager.getPlugins(), hasSize(3));
1277         assertNotNull(manager.getPlugin("test.restartrequired"));
1278         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1279         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1280         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1281 
1282         // Some filesystems only record last modified in seconds
1283         Thread.sleep(1000);
1284         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1285                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1286                 "    </plugin-info>", "    <requiresRestart key='foo' />", "    <requiresRestart key='bar' />", "</atlassian-plugin>").build();
1287 
1288         // Install version 2 of the plugin
1289         manager.installPlugin(new JarPluginArtifact(updateFile));
1290 
1291         assertThat(manager.getPlugins(), hasSize(3));
1292         assertNotNull(manager.getPlugin("test.restartrequired"));
1293         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1294         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1295         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1296 
1297         Thread.sleep(1000);
1298         final File updateFile2 = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1299                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>3.0</version>",
1300                 "    </plugin-info>", "    <requiresRestart key='foo' />", "    <requiresRestart key='bar' />", "</atlassian-plugin>").build();
1301 
1302         // Install version 3 of the plugin
1303         manager.installPlugin(new JarPluginArtifact(updateFile2));
1304 
1305         assertThat(manager.getPlugins(), hasSize(3));
1306         assertNotNull(manager.getPlugin("test.restartrequired"));
1307         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1308         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1309         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1310 
1311         // Lets revert the whole upgrade so that the original plugin is restored
1312         manager.revertRestartRequiredChange("test.restartrequired");
1313         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1314         assertEquals("1.0", manager.getPlugin("test.restartrequired").getPluginInformation().getVersion());
1315 
1316         manager.shutdown();
1317         manager.init();
1318 
1319         assertThat(manager.getPlugins(), hasSize(3));
1320         assertNotNull(manager.getPlugin("test.restartrequired"));
1321         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1322         assertEquals("1.0", manager.getPlugin("test.restartrequired").getPluginInformation().getVersion());
1323         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1324         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1325     }
1326 
1327     @Test
1328     public void testUpgradePluginThatRequiresRestartMultipleTimeStaysUpgraded() throws PluginParseException, IOException, InterruptedException
1329     {
1330         createFillAndCleanTempPluginDirectory();
1331         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1332 
1333         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1334                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1335                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1336 
1337         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1338         manager.setPluginInstaller(new FilePluginInstaller(pluginsTestDir));
1339 
1340         assertThat(manager.getPlugins(), hasSize(3));
1341         assertNotNull(manager.getPlugin("test.restartrequired"));
1342         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1343         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1344         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1345 
1346         // Some filesystems only record last modified in seconds
1347         Thread.sleep(1000);
1348         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1349                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1350                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build();
1351 
1352         manager.installPlugin(new JarPluginArtifact(updateFile));
1353 
1354         assertThat(manager.getPlugins(), hasSize(3));
1355         assertNotNull(manager.getPlugin("test.restartrequired"));
1356         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1357         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1358         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1359 
1360         Thread.sleep(1000);
1361         final File updateFile2 = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1362                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>3.0</version>",
1363                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build();
1364 
1365         manager.installPlugin(new JarPluginArtifact(updateFile2));
1366 
1367         assertThat(manager.getPlugins(), hasSize(3));
1368         assertNotNull(manager.getPlugin("test.restartrequired"));
1369         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1370         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1371         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1372 
1373         manager.shutdown();
1374         manager.init();
1375 
1376         assertThat(manager.getPlugins(), hasSize(3));
1377         assertNotNull(manager.getPlugin("test.restartrequired"));
1378         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1379         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1380         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1381     }
1382 
1383     @Test
1384     public void testUpgradePluginThatPreviouslyRequiredRestart() throws PluginParseException, IOException, InterruptedException
1385     {
1386         createFillAndCleanTempPluginDirectory();
1387         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1388 
1389         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1390                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1391                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1392 
1393         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1394 
1395         assertThat(manager.getPlugins(), hasSize(3));
1396         assertNotNull(manager.getPlugin("test.restartrequired"));
1397         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1398         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1399         assertEquals(PluginRestartState.NONE, manager.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>", "</atlassian-plugin>").build(pluginsTestDir);
1406 
1407         origFile.delete();
1408         FileUtils.moveFile(updateFile, origFile);
1409 
1410         manager.scanForNewPlugins();
1411         assertThat(manager.getPlugins(), hasSize(3));
1412         assertNotNull(manager.getPlugin("test.restartrequired"));
1413         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1414         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1415         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1416 
1417         manager.shutdown();
1418         manager.init();
1419 
1420         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1421         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1422     }
1423 
1424     @Test
1425     public void testInstallPluginThatPreviouslyRequiredRestart() throws PluginParseException, IOException, InterruptedException
1426     {
1427         createFillAndCleanTempPluginDirectory();
1428         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1429 
1430         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1431 
1432         assertThat(manager.getPlugins(), hasSize(2));
1433         assertNull(manager.getPlugin("test.restartrequired"));
1434 
1435         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1436                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1437                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1438 
1439         manager.scanForNewPlugins();
1440         assertThat(manager.getPlugins(), hasSize(3));
1441         assertNotNull(manager.getPlugin("test.restartrequired"));
1442         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1443         assertEquals(PluginRestartState.INSTALL, manager.getPluginRestartState("test.restartrequired"));
1444 
1445         // Some filesystems only record last modified in seconds
1446         Thread.sleep(1000);
1447         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1448                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1449                 "    </plugin-info>", "</atlassian-plugin>").build(pluginsTestDir);
1450 
1451         origFile.delete();
1452         FileUtils.moveFile(updateFile, origFile);
1453 
1454         manager.scanForNewPlugins();
1455         assertThat(manager.getPlugins(), hasSize(3));
1456         assertNotNull(manager.getPlugin("test.restartrequired"));
1457         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1458         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1459         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1460 
1461         manager.shutdown();
1462         manager.init();
1463 
1464         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1465         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1466     }
1467 
1468     @Test
1469     public void testInstallPluginMoreThanOnceStaysAsInstall() throws PluginParseException, IOException, InterruptedException
1470     {
1471         createFillAndCleanTempPluginDirectory();
1472         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1473 
1474         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1475 
1476         assertThat(manager.getPlugins(), hasSize(2));
1477         assertNull(manager.getPlugin("test.restartrequired"));
1478         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1479 
1480         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1481                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1482                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1483 
1484         manager.scanForNewPlugins();
1485         assertThat(manager.getPlugins(), hasSize(3));
1486         assertNotNull(manager.getPlugin("test.restartrequired"));
1487         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1488         assertEquals(PluginRestartState.INSTALL, manager.getPluginRestartState("test.restartrequired"));
1489 
1490         // Some filesystems only record last modified in seconds
1491         Thread.sleep(1000);
1492         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1493                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1494                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1495 
1496         origFile.delete();
1497         FileUtils.moveFile(updateFile, origFile);
1498 
1499         manager.scanForNewPlugins();
1500         assertThat(manager.getPlugins(), hasSize(3));
1501         assertNotNull(manager.getPlugin("test.restartrequired"));
1502         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1503         assertEquals(PluginRestartState.INSTALL, manager.getPluginRestartState("test.restartrequired"));
1504 
1505         manager.shutdown();
1506         manager.init();
1507 
1508         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1509         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1510     }
1511 
1512     @Test
1513     public void testRemovePluginThatRequiresRestart() throws PluginParseException, IOException
1514     {
1515         createFillAndCleanTempPluginDirectory();
1516         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1517 
1518         final File pluginFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1519                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1520                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1521 
1522         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1523 
1524         assertThat(manager.getPlugins(), hasSize(3));
1525         assertNotNull(manager.getPlugin("test.restartrequired"));
1526         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1527         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1528         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1529 
1530         manager.uninstall(manager.getPlugin("test.restartrequired"));
1531 
1532         assertThat(manager.getPlugins(), hasSize(3));
1533         assertNotNull(manager.getPlugin("test.restartrequired"));
1534         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1535         assertEquals(PluginRestartState.REMOVE, manager.getPluginRestartState("test.restartrequired"));
1536         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1537 
1538         manager.shutdown();
1539         manager.init();
1540 
1541         assertFalse(pluginFile.exists());
1542         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1543         assertThat(manager.getPlugins(), hasSize(2));
1544     }
1545 
1546     @Test
1547     public void testRemovePluginThatRequiresRestartThenReverted() throws PluginParseException, IOException
1548     {
1549         createFillAndCleanTempPluginDirectory();
1550         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1551 
1552         final File pluginFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1553                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1554                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1555 
1556         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1557 
1558         assertThat(manager.getPlugins(), hasSize(3));
1559         assertNotNull(manager.getPlugin("test.restartrequired"));
1560         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1561         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1562         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1563 
1564         manager.uninstall(manager.getPlugin("test.restartrequired"));
1565 
1566         assertThat(manager.getPlugins(), hasSize(3));
1567         assertNotNull(manager.getPlugin("test.restartrequired"));
1568         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1569         assertEquals(PluginRestartState.REMOVE, manager.getPluginRestartState("test.restartrequired"));
1570         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1571 
1572         manager.revertRestartRequiredChange("test.restartrequired");
1573         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1574 
1575         manager.shutdown();
1576         manager.init();
1577 
1578         assertThat(manager.getPlugins(), hasSize(3));
1579         assertNotNull(manager.getPlugin("test.restartrequired"));
1580         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1581         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1582         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1583     }
1584 
1585     @Test
1586     public void testRemovePluginThatRequiresRestartViaSubclass() throws PluginParseException, IOException
1587     {
1588         createFillAndCleanTempPluginDirectory();
1589         moduleDescriptorFactory.addModuleDescriptor("requiresRestartSubclass", RequiresRestartSubclassModuleDescriptor.class);
1590 
1591         final File pluginFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1592                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1593                 "    </plugin-info>", "    <requiresRestartSubclass key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1594 
1595         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1596 
1597         assertThat(manager.getPlugins(), hasSize(3));
1598         assertNotNull(manager.getPlugin("test.restartrequired"));
1599         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1600         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartSubclassModuleDescriptor.class), hasSize(1));
1601         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1602 
1603         manager.uninstall(manager.getPlugin("test.restartrequired"));
1604 
1605         assertThat(manager.getPlugins(), hasSize(3));
1606         assertNotNull(manager.getPlugin("test.restartrequired"));
1607         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1608         assertEquals(PluginRestartState.REMOVE, manager.getPluginRestartState("test.restartrequired"));
1609         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartSubclassModuleDescriptor.class), hasSize(1));
1610 
1611         manager.shutdown();
1612         manager.init();
1613 
1614         assertFalse(pluginFile.exists());
1615         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartSubclassModuleDescriptor.class), hasSize(0));
1616         assertThat(manager.getPlugins(), hasSize(2));
1617     }
1618 
1619     @Test
1620     public void testDisableEnableOfPluginThatRequiresRestart() throws PluginParseException, IOException
1621     {
1622         createFillAndCleanTempPluginDirectory();
1623         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1624 
1625         new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1626                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1627                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1628 
1629         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1630 
1631         assertThat(manager.getPlugins(), hasSize(3));
1632         assertNotNull(manager.getPlugin("test.restartrequired"));
1633         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1634         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1635         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1636 
1637         manager.disablePlugin("test.restartrequired");
1638         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1639         manager.enablePlugins("test.restartrequired");
1640 
1641         assertThat(manager.getPlugins(), hasSize(3));
1642         assertNotNull(manager.getPlugin("test.restartrequired"));
1643         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1644         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1645         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1646     }
1647 
1648     @Test
1649     public void testCannotRemovePluginFromStaticLoader() throws PluginParseException, IOException
1650     {
1651         createFillAndCleanTempPluginDirectory();
1652         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1653 
1654         directoryPluginLoader = new DirectoryPluginLoader(
1655                 pluginsTestDir,
1656                 ImmutableList.<PluginFactory>of(new LegacyDynamicPluginFactory(PluginAccessor.Descriptor.FILENAME), new XmlDynamicPluginFactory(new MockApplication().setKey("key"))),
1657                 pluginEventManager);
1658 
1659         manager = newDefaultPluginManager(directoryPluginLoader, new SinglePluginLoader("test-requiresRestart-plugin.xml"));
1660         manager.init();
1661 
1662         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1663         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin"));
1664         assertTrue(getPluginAccessor().isPluginEnabled("test.atlassian.plugin"));
1665         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1666         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.atlassian.plugin"));
1667 
1668         try
1669         {
1670             manager.uninstall(getPluginAccessor().getPlugin("test.atlassian.plugin"));
1671             fail();
1672         }
1673         catch (final PluginException ex)
1674         {
1675             // test passed
1676         }
1677 
1678         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1679         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin"));
1680         assertTrue(getPluginAccessor().isPluginEnabled("test.atlassian.plugin"));
1681         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.atlassian.plugin"));
1682         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1683     }
1684 
1685     private DefaultPluginManager makeClassLoadingPluginManager(PluginLoader... pluginLoaders) throws PluginParseException
1686     {
1687         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
1688 
1689         directoryPluginLoader = new DirectoryPluginLoader(pluginsTestDir, ImmutableList.<PluginFactory>of(
1690                 new LegacyDynamicPluginFactory(PluginAccessor.Descriptor.FILENAME),
1691                 new XmlDynamicPluginFactory(new MockApplication().setKey("key"))), pluginEventManager);
1692 
1693         final DefaultPluginManager manager = newDefaultPluginManager(Iterables.toArray(ImmutableList.<PluginLoader>builder().add(directoryPluginLoader).addAll(copyOf(pluginLoaders)).build(), PluginLoader.class));
1694 
1695         manager.init();
1696         return manager;
1697     }
1698 
1699     @Test
1700     public void testRemovingPlugins() throws PluginException, IOException
1701     {
1702         createFillAndCleanTempPluginDirectory();
1703 
1704         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1705         assertThat(manager.getPlugins(), hasSize(2));
1706         final MockAnimalModuleDescriptor moduleDescriptor = (MockAnimalModuleDescriptor) manager.getPluginModule("test.atlassian.plugin.classloaded:paddington");
1707         assertFalse(moduleDescriptor.disabled);
1708         final PassListener disabledListener = new PassListener(PluginDisabledEvent.class);
1709         pluginEventManager.register(disabledListener);
1710         final Plugin plugin = manager.getPlugin("test.atlassian.plugin.classloaded");
1711         manager.uninstall(plugin);
1712         assertTrue("Module must have had disable() called before being removed", moduleDescriptor.disabled);
1713 
1714         // uninstalling a plugin should remove it's state completely from the state store - PLUG-13
1715         assertTrue(pluginStateStore.load().getPluginStateMap(plugin).isEmpty());
1716 
1717         assertThat(manager.getPlugins(), hasSize(1));
1718         // plugin is no longer available though the plugin manager
1719         assertNull(manager.getPlugin("test.atlassian.plugin.classloaded"));
1720         assertEquals(1, pluginsTestDir.listFiles().length);
1721         disabledListener.assertCalled();
1722     }
1723 
1724     @Test
1725     public void testPluginModuleAvailableAfterInstallation()
1726     {
1727         PluginLoader pluginLoader = mock(PluginLoader.class);
1728         when(pluginLoader.supportsRemoval()).thenReturn(true);
1729         Plugin plugin = mock(Plugin.class);
1730         when(plugin.getKey()).thenReturn("dynPlugin");
1731         when(plugin.isEnabledByDefault()).thenReturn(true);
1732         when(plugin.isDeleteable()).thenReturn(true);
1733         when(plugin.isUninstallable()).thenReturn(true);
1734         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1735         when(plugin.compareTo(any(Plugin.class))).thenReturn(-1);
1736         when (pluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(asList(plugin));
1737 
1738         manager = newDefaultPluginManager(pluginLoader);
1739         manager.init();
1740 
1741         PluginModuleEnabledListener listener = new PluginModuleEnabledListener();
1742         pluginEventManager.register(listener);
1743         Collection<ModuleDescriptor<?>> mods = new ArrayList<ModuleDescriptor<?>>();
1744         MockModuleDescriptor<String> moduleDescriptor = new MockModuleDescriptor<String>(plugin, "foo", "foo");
1745         mods.add(moduleDescriptor);
1746         when(plugin.getModuleDescriptors()).thenReturn(mods);
1747         when(plugin.getModuleDescriptor("foo")).thenReturn((ModuleDescriptor)moduleDescriptor);
1748         pluginEventManager.broadcast(new PluginModuleAvailableEvent(moduleDescriptor));
1749 
1750         assertTrue(getPluginAccessor().isPluginModuleEnabled("dynPlugin:foo"));
1751         assertTrue(listener.called);
1752     }
1753 
1754     @Test
1755     public void testPluginModuleAvailableAfterInstallationButConfiguredToBeDisabled()
1756     {
1757         PluginLoader pluginLoader = mock(PluginLoader.class);
1758         when(pluginLoader.supportsRemoval()).thenReturn(true);
1759         Plugin plugin = mock(Plugin.class);
1760         when(plugin.getKey()).thenReturn("dynPlugin");
1761         when(plugin.isEnabledByDefault()).thenReturn(true);
1762         when(plugin.isDeleteable()).thenReturn(true);
1763         when(plugin.isUninstallable()).thenReturn(true);
1764         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1765         when(plugin.compareTo(any(Plugin.class))).thenReturn(-1);
1766         when (pluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(asList(plugin));
1767 
1768         manager = newDefaultPluginManager(pluginLoader);
1769         manager.init();
1770 
1771         MockModuleDescriptor<String> moduleDescriptor = new MockModuleDescriptor<String>(plugin, "foo", "foo");
1772 
1773         manager.disablePluginModuleState(moduleDescriptor, manager.getStore());
1774 
1775         PluginModuleEnabledListener listener = new PluginModuleEnabledListener();
1776         pluginEventManager.register(listener);
1777         Collection<ModuleDescriptor<?>> mods = new ArrayList<ModuleDescriptor<?>>();
1778         mods.add(moduleDescriptor);
1779 
1780         when(plugin.getModuleDescriptors()).thenReturn(mods);
1781         when(plugin.getModuleDescriptor("foo")).thenReturn((ModuleDescriptor)moduleDescriptor);
1782         pluginEventManager.broadcast(new PluginModuleAvailableEvent(moduleDescriptor));
1783 
1784         assertFalse(getPluginAccessor().isPluginModuleEnabled("dynPlugin:foo"));
1785         assertFalse(listener.called);
1786     }
1787 
1788     @Test
1789     public void testPluginModuleUnavailableAfterInstallation()
1790     {
1791         PluginLoader pluginLoader = mock(PluginLoader.class);
1792         when(pluginLoader.supportsRemoval()).thenReturn(true);
1793         Plugin plugin = mock(Plugin.class);
1794         when(plugin.getKey()).thenReturn("dynPlugin");
1795         when(plugin.isEnabledByDefault()).thenReturn(true);
1796         when(plugin.isDeleteable()).thenReturn(true);
1797         when(plugin.isUninstallable()).thenReturn(true);
1798         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1799         when(plugin.compareTo(any(Plugin.class))).thenReturn(-1);
1800         when (pluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(asList(plugin));
1801 
1802         manager = newDefaultPluginManager(pluginLoader);
1803         manager.init();
1804 
1805         PluginModuleDisabledListener listener = new PluginModuleDisabledListener();
1806         pluginEventManager.register(listener);
1807         Collection<ModuleDescriptor<?>> mods = new ArrayList<ModuleDescriptor<?>>();
1808         MockModuleDescriptor<String> moduleDescriptor = new MockModuleDescriptor<String>(plugin, "foo", "foo");
1809         mods.add(moduleDescriptor);
1810         when(plugin.getModuleDescriptors()).thenReturn(mods);
1811         when(plugin.getModuleDescriptor("foo")).thenReturn((ModuleDescriptor)moduleDescriptor);
1812         pluginEventManager.broadcast(new PluginModuleAvailableEvent(moduleDescriptor));
1813         assertTrue(getPluginAccessor().isPluginModuleEnabled("dynPlugin:foo"));
1814         assertFalse(listener.called);
1815         pluginEventManager.broadcast(new PluginModuleUnavailableEvent(moduleDescriptor));
1816         assertTrue(listener.called);
1817     }
1818 
1819     @Test
1820     public void testPluginContainerUnavailable()
1821     {
1822         PluginLoader pluginLoader = mock(PluginLoader.class);
1823         when(pluginLoader.supportsRemoval()).thenReturn(true);
1824         Plugin plugin = mock(Plugin.class);
1825         when(plugin.getKey()).thenReturn("dynPlugin");
1826         when(plugin.isEnabledByDefault()).thenReturn(true);
1827         when(plugin.isDeleteable()).thenReturn(true);
1828         when(plugin.isUninstallable()).thenReturn(true);
1829         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1830         when(plugin.compareTo(any(Plugin.class))).thenReturn(-1);
1831         Collection<ModuleDescriptor<?>> mods = new ArrayList<ModuleDescriptor<?>>();
1832         MockModuleDescriptor<String> moduleDescriptor = new MockModuleDescriptor<String>(plugin, "foo", "foo");
1833         mods.add(moduleDescriptor);
1834         when(plugin.getModuleDescriptors()).thenReturn(mods);
1835         when(plugin.getModuleDescriptor("foo")).thenReturn((ModuleDescriptor)moduleDescriptor);
1836         when (pluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(asList(plugin));
1837 
1838         manager = newDefaultPluginManager(pluginLoader);
1839         manager.init();
1840 
1841         PluginDisabledListener listener = new PluginDisabledListener();
1842         PluginModuleDisabledListener moduleDisabledListener = new PluginModuleDisabledListener();
1843         pluginEventManager.register(listener);
1844         pluginEventManager.register(moduleDisabledListener);
1845         when(plugin.getPluginState()).thenReturn(PluginState.DISABLED);
1846         pluginEventManager.broadcast(new PluginContainerUnavailableEvent("dynPlugin"));
1847         //Fix in behaviour, if the plugin is already disabled and you try to disable it there should be no event called
1848         assertFalse(getPluginAccessor().isPluginEnabled("dynPlugin"));
1849         assertFalse(listener.called);
1850         assertFalse(moduleDisabledListener.called);
1851     }
1852 
1853     @Test
1854     public void testUninstallPluginWithDependencies() throws PluginException, IOException
1855     {
1856         PluginLoader pluginLoader = mock(PluginLoader.class);
1857         when(pluginLoader.supportsRemoval()).thenReturn(true);
1858         Plugin child = mock(Plugin.class);
1859         when(child.getKey()).thenReturn("child");
1860         when(child.isEnabledByDefault()).thenReturn(true);
1861         when(child.getPluginState()).thenReturn(PluginState.ENABLED);
1862         when(child.getRequiredPlugins()).thenReturn(singleton("parent"));
1863         when(child.compareTo(any(Plugin.class))).thenReturn(-1);
1864         Plugin parent = mock(Plugin.class);
1865         when(parent.getKey()).thenReturn("parent");
1866         when(parent.isEnabledByDefault()).thenReturn(true);
1867         when(parent.isDeleteable()).thenReturn(true);
1868         when(parent.isUninstallable()).thenReturn(true);
1869         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
1870         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
1871         when (pluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(asList(child, parent));
1872 
1873         manager = newDefaultPluginManager(pluginLoader);
1874         manager.init();
1875 
1876         manager.uninstall(parent);
1877         verify(parent).enable();
1878         verify(parent).disable();
1879         verify(pluginLoader).removePlugin(parent);
1880 
1881         verify(child).enable();
1882         verify(child).disable();
1883     }
1884 
1885     @Test
1886     public void testUninstallPluginWithMultiLevelDependencies() throws PluginException, IOException
1887     {
1888         PluginLoader pluginLoader = mock(PluginLoader.class);
1889         when(pluginLoader.supportsRemoval()).thenReturn(true);
1890 
1891         Plugin child = mock(Plugin.class);
1892         when(child.getKey()).thenReturn("child");
1893         when(child.isEnabledByDefault()).thenReturn(true);
1894         when(child.getPluginState()).thenReturn(PluginState.ENABLED);
1895         when(child.getRequiredPlugins()).thenReturn(singleton("parent"));
1896         when(child.compareTo(any(Plugin.class))).thenReturn(-1);
1897 
1898         Plugin parent = mock(Plugin.class);
1899         when(parent.getKey()).thenReturn("parent");
1900         when(parent.isEnabledByDefault()).thenReturn(true);
1901         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
1902         when(parent.getRequiredPlugins()).thenReturn(singleton("grandparent"));
1903         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
1904 
1905         Plugin grandparent = mock(Plugin.class);
1906         when(grandparent.getKey()).thenReturn("grandparent");
1907         when(grandparent.isEnabledByDefault()).thenReturn(true);
1908         when(grandparent.isDeleteable()).thenReturn(true);
1909         when(grandparent.isUninstallable()).thenReturn(true);
1910         when(grandparent.getPluginState()).thenReturn(PluginState.ENABLED);
1911         when(grandparent.compareTo(any(Plugin.class))).thenReturn(-1);
1912         when(pluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(asList(child, parent, grandparent));
1913         manager = newDefaultPluginManager(pluginLoader);
1914         manager.init();
1915 
1916         manager.uninstall(grandparent);
1917         verify(grandparent).enable();
1918         verify(grandparent).disable();
1919         verify(pluginLoader).removePlugin(grandparent);
1920 
1921         verify(parent).enable();
1922         verify(parent).disable();
1923         verify(child).enable();
1924         verify(child).disable();
1925     }
1926 
1927     @Test
1928     public void testCircularDependencyWouldNotCauseInfiniteLoop() throws PluginException, IOException
1929     {
1930         PluginLoader pluginLoader = mock(PluginLoader.class);
1931         when(pluginLoader.supportsRemoval()).thenReturn(true);
1932 
1933         Plugin p1 = mock(Plugin.class);
1934         when(p1.getKey()).thenReturn("p1");
1935         when(p1.isEnabledByDefault()).thenReturn(true);
1936         when(p1.getPluginState()).thenReturn(PluginState.ENABLED);
1937         when(p1.getRequiredPlugins()).thenReturn(ImmutableSet.of("p2", "parent"));
1938         when(p1.compareTo(any(Plugin.class))).thenReturn(-1);
1939 
1940         // Create a circular dependency between p1 and p2. This should not happen, but test anyway.
1941         Plugin p2 = mock(Plugin.class);
1942         when(p2.getKey()).thenReturn("p2");
1943         when(p2.isEnabledByDefault()).thenReturn(true);
1944         when(p2.getPluginState()).thenReturn(PluginState.ENABLED);
1945         when(p2.getRequiredPlugins()).thenReturn(singleton("p1"));
1946         when(p2.compareTo(any(Plugin.class))).thenReturn(-1);
1947 
1948         Plugin parent = mock(Plugin.class);
1949         when(parent.getKey()).thenReturn("parent");
1950         when(parent.isEnabledByDefault()).thenReturn(true);
1951         when(parent.isDeleteable()).thenReturn(true);
1952         when(parent.isUninstallable()).thenReturn(true);
1953         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
1954         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
1955         when (pluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(asList(p1, p2, parent));
1956         manager = newDefaultPluginManager(pluginLoader);
1957         manager.init();
1958 
1959         manager.uninstall(parent);
1960         verify(parent, times(1)).enable();
1961         verify(parent, times(1)).disable();
1962         verify(pluginLoader).removePlugin(parent);
1963 
1964         verify(p1, times(1)).enable();
1965         verify(p1, times(1)).disable();
1966         verify(p2, times(1)).enable();
1967         verify(p2, times(1)).disable();
1968     }
1969 
1970     @Test
1971     public void testThreeCycleDependencyWouldNotCauseInfiniteLoop() throws PluginException, IOException
1972     {
1973         PluginLoader pluginLoader = mock(PluginLoader.class);
1974         when(pluginLoader.supportsRemoval()).thenReturn(true);
1975 
1976         Plugin p1 = mock(Plugin.class);
1977         when(p1.getKey()).thenReturn("p1");
1978         when(p1.isEnabledByDefault()).thenReturn(true);
1979         when(p1.getPluginState()).thenReturn(PluginState.ENABLED);
1980         when(p1.getRequiredPlugins()).thenReturn(ImmutableSet.of("p2", "parent"));
1981         when(p1.compareTo(any(Plugin.class))).thenReturn(-1);
1982 
1983         // Create a circular dependency between p1, p2 and p3. This should not happen, but test anyway.
1984         Plugin p2 = mock(Plugin.class);
1985         when(p2.getKey()).thenReturn("p2");
1986         when(p2.isEnabledByDefault()).thenReturn(true);
1987         when(p2.getPluginState()).thenReturn(PluginState.ENABLED);
1988         when(p2.getRequiredPlugins()).thenReturn(singleton("p3"));
1989         when(p2.compareTo(any(Plugin.class))).thenReturn(-1);
1990 
1991         Plugin p3 = mock(Plugin.class);
1992         when(p3.getKey()).thenReturn("p3");
1993         when(p3.isEnabledByDefault()).thenReturn(true);
1994         when(p3.getPluginState()).thenReturn(PluginState.ENABLED);
1995         when(p3.getRequiredPlugins()).thenReturn(singleton("p1"));
1996         when(p3.compareTo(any(Plugin.class))).thenReturn(-1);
1997 
1998         Plugin parent = mock(Plugin.class);
1999         when(parent.getKey()).thenReturn("parent");
2000         when(parent.isEnabledByDefault()).thenReturn(true);
2001         when(parent.isDeleteable()).thenReturn(true);
2002         when(parent.isUninstallable()).thenReturn(true);
2003         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
2004         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
2005         when (pluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(asList(p1, p2, p3, parent));
2006         manager = newDefaultPluginManager(pluginLoader);
2007         manager.init();
2008 
2009         manager.uninstall(parent);
2010         verify(parent, times(1)).enable();
2011         verify(parent, times(1)).disable();
2012         verify(pluginLoader).removePlugin(parent);
2013 
2014         verify(p1, times(1)).enable();
2015         verify(p1, times(1)).disable();
2016         verify(p2, times(1)).enable();
2017         verify(p2, times(1)).disable();
2018         verify(p3, times(1)).enable();
2019         verify(p3, times(1)).disable();
2020     }
2021 
2022     @Test
2023     public void testNonRemovablePlugins() throws PluginParseException
2024     {
2025         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
2026         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
2027 
2028         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
2029         manager.init();
2030 
2031         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
2032         assertFalse(plugin.isUninstallable());
2033         assertNotNull(plugin.getResourceAsStream("test-atlassian-plugin.xml"));
2034 
2035         try
2036         {
2037             manager.uninstall(plugin);
2038             fail("Where was the exception?");
2039         }
2040         catch (final PluginException p)
2041         {
2042         }
2043     }
2044 
2045     @Test
2046     public void testNonDeletablePlugins() throws PluginException, IOException
2047     {
2048         createFillAndCleanTempPluginDirectory();
2049 
2050         final DefaultPluginManager manager = makeClassLoadingPluginManager();
2051         assertThat(manager.getPlugins(), hasSize(2));
2052 
2053         // Set plugin file can't be deleted.
2054         final Plugin pluginToRemove = new AbstractDelegatingPlugin(manager.getPlugin("test.atlassian.plugin.classloaded"))
2055         {
2056             public boolean isDeleteable()
2057             {
2058                 return false;
2059             }
2060         };
2061 
2062         // Disable plugin module before uninstall
2063         final MockAnimalModuleDescriptor moduleDescriptor = (MockAnimalModuleDescriptor) manager.getPluginModule("test.atlassian.plugin.classloaded:paddington");
2064         assertFalse(moduleDescriptor.disabled);
2065 
2066         manager.uninstall(pluginToRemove);
2067 
2068         assertTrue("Module must have had disable() called before being removed", moduleDescriptor.disabled);
2069         assertThat(manager.getPlugins(), hasSize(1));
2070         assertNull(manager.getPlugin("test.atlassian.plugin.classloaded"));
2071         assertEquals(2, pluginsTestDir.listFiles().length);
2072     }
2073 
2074     // These methods test the plugin compareTo() function, which compares plugins based on their version numbers.
2075     @Test
2076     public void testComparePluginNewer()
2077     {
2078 
2079         final Plugin p1 = createPluginWithVersion("1.1");
2080         final Plugin p2 = createPluginWithVersion("1.0");
2081         assertTrue(p1.compareTo(p2) == 1);
2082 
2083         p1.getPluginInformation().setVersion("1.10");
2084         p2.getPluginInformation().setVersion("1.2");
2085         assertTrue(p1.compareTo(p2) == 1);
2086 
2087         p1.getPluginInformation().setVersion("1.2");
2088         p2.getPluginInformation().setVersion("1.01");
2089         assertTrue(p1.compareTo(p2) == 1);
2090 
2091         p1.getPluginInformation().setVersion("1.0.1");
2092         p2.getPluginInformation().setVersion("1.0");
2093         assertTrue(p1.compareTo(p2) == 1);
2094 
2095         p1.getPluginInformation().setVersion("1.2");
2096         p2.getPluginInformation().setVersion("1.1.1");
2097         assertTrue(p1.compareTo(p2) == 1);
2098     }
2099 
2100     @Test
2101     public void testComparePluginOlder()
2102     {
2103         final Plugin p1 = createPluginWithVersion("1.0");
2104         final Plugin p2 = createPluginWithVersion("1.1");
2105         assertTrue(p1.compareTo(p2) == -1);
2106 
2107         p1.getPluginInformation().setVersion("1.2");
2108         p2.getPluginInformation().setVersion("1.10");
2109         assertTrue(p1.compareTo(p2) == -1);
2110 
2111         p1.getPluginInformation().setVersion("1.01");
2112         p2.getPluginInformation().setVersion("1.2");
2113         assertTrue(p1.compareTo(p2) == -1);
2114 
2115         p1.getPluginInformation().setVersion("1.0");
2116         p2.getPluginInformation().setVersion("1.0.1");
2117         assertTrue(p1.compareTo(p2) == -1);
2118 
2119         p1.getPluginInformation().setVersion("1.1.1");
2120         p2.getPluginInformation().setVersion("1.2");
2121         assertTrue(p1.compareTo(p2) == -1);
2122     }
2123 
2124     @Test
2125     public void testComparePluginEqual()
2126     {
2127         final Plugin p1 = createPluginWithVersion("1.0");
2128         final Plugin p2 = createPluginWithVersion("1.0");
2129         assertTrue(p1.compareTo(p2) == 0);
2130 
2131         p1.getPluginInformation().setVersion("1.1.0.0");
2132         p2.getPluginInformation().setVersion("1.1");
2133         assertTrue(p1.compareTo(p2) == 0);
2134 
2135         p1.getPluginInformation().setVersion(" 1 . 1 ");
2136         p2.getPluginInformation().setVersion("1.1");
2137         assertTrue(p1.compareTo(p2) == 0);
2138     }
2139 
2140     // If we can't understand the version of a plugin, then take the new one.
2141     @Test
2142     public void testComparePluginNoVersion()
2143     {
2144         final Plugin p1 = createPluginWithVersion("1.0");
2145         final Plugin p2 = createPluginWithVersion("#$%");
2146         assertEquals(1, p1.compareTo(p2));
2147 
2148         p1.getPluginInformation().setVersion("#$%");
2149         p2.getPluginInformation().setVersion("1.0");
2150         assertEquals(-1, p1.compareTo(p2));
2151     }
2152 
2153     @Test
2154     public void testComparePluginBadPlugin()
2155     {
2156         final Plugin p1 = createPluginWithVersion("1.0");
2157         final Plugin p2 = createPluginWithVersion("1.0");
2158 
2159         // Compare against something with a different key
2160         p2.setKey("bad.key");
2161         assertTrue(p1.compareTo(p2) != 0);
2162     }
2163 
2164     @Test
2165     public void testInvalidationOfDynamicResourceCache() throws IOException, PluginException
2166     {
2167         createFillAndCleanTempPluginDirectory();
2168 
2169         final DefaultPluginManager manager = makeClassLoadingPluginManager();
2170 
2171         checkResources(manager, true, true);
2172         manager.disablePlugin("test.atlassian.plugin.classloaded");
2173         checkResources(manager, false, false);
2174         manager.enablePlugin("test.atlassian.plugin.classloaded");
2175         checkResources(manager, true, true);
2176         manager.uninstall(manager.getPlugin("test.atlassian.plugin.classloaded"));
2177         checkResources(manager, false, false);
2178         //restore paddington to test plugins dir
2179         FileUtils.copyDirectory(pluginsDirectory, pluginsTestDir);
2180         manager.scanForNewPlugins();
2181         checkResources(manager, true, true);
2182         // Resources from disabled modules are still available
2183         //manager.disablePluginModule("test.atlassian.plugin.classloaded:paddington");
2184         //checkResources(manager, true, false);
2185     }
2186 
2187     @Test
2188     public void testValidatePlugin() throws PluginParseException
2189     {
2190         final DynamicPluginLoader mockLoader = mock(DynamicPluginLoader.class);
2191         when(mockLoader.isDynamicPluginLoader()).thenReturn(true);
2192 
2193         manager = new DefaultPluginManager(pluginStateStore, ImmutableList.<PluginLoader>of(mockLoader), moduleDescriptorFactory, new DefaultPluginEventManager());
2194 
2195         final PluginArtifact mockPluginJar = mock(PluginArtifact.class);
2196         final PluginArtifact pluginArtifact = mockPluginJar;
2197         when(mockLoader.canLoad(pluginArtifact)).thenReturn("foo");
2198 
2199         final String key = manager.validatePlugin(pluginArtifact);
2200         assertEquals("foo", key);
2201         verify(mockLoader).canLoad(pluginArtifact);
2202     }
2203 
2204     @Test
2205     public void testValidatePluginWithNoDynamicLoaders() throws PluginParseException
2206     {
2207         final PluginLoader loader = mock(PluginLoader.class);
2208         final DefaultPluginManager manager = new DefaultPluginManager(pluginStateStore, ImmutableList.<PluginLoader>of(loader), moduleDescriptorFactory, new DefaultPluginEventManager());
2209 
2210         final PluginArtifact pluginArtifact = mock(PluginArtifact.class);
2211         try
2212         {
2213             manager.validatePlugin(pluginArtifact);
2214             fail("Should have thrown exception");
2215         }
2216         catch (final IllegalStateException ex)
2217         {
2218             // test passed
2219         }
2220     }
2221 
2222     @Test
2223     public void testInvalidationOfDynamicClassCache() throws IOException, PluginException
2224     {
2225         createFillAndCleanTempPluginDirectory();
2226 
2227         final DefaultPluginManager manager = makeClassLoadingPluginManager();
2228 
2229         checkClasses(manager, true);
2230         manager.disablePlugin("test.atlassian.plugin.classloaded");
2231         checkClasses(manager, false);
2232         manager.enablePlugin("test.atlassian.plugin.classloaded");
2233         checkClasses(manager, true);
2234         manager.uninstall(manager.getPlugin("test.atlassian.plugin.classloaded"));
2235         checkClasses(manager, false);
2236         //restore paddington to test plugins dir
2237         FileUtils.copyDirectory(pluginsDirectory, pluginsTestDir);
2238         manager.scanForNewPlugins();
2239         checkClasses(manager, true);
2240     }
2241 
2242     @Test
2243     public void testInstallPlugin() throws Exception
2244     {
2245         final PluginPersistentStateStore mockPluginStateStore = mock(PluginPersistentStateStore.class);
2246         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
2247         final DynamicPluginLoader mockPluginLoader = mock(DynamicPluginLoader.class);
2248         when(mockPluginLoader.isDynamicPluginLoader()).thenReturn(true);
2249 
2250         final DescriptorParserFactory mockDescriptorParserFactory = mock(DescriptorParserFactory.class);
2251         final DescriptorParser mockDescriptorParser = mock(DescriptorParser.class);
2252         final PluginArtifact pluginArtifact = mock(PluginArtifact.class);
2253         final PluginInstaller mockRepository = mock(PluginInstaller.class);
2254         final Plugin plugin = mock(Plugin.class);
2255 
2256 
2257         final DefaultPluginManager pluginManager = new DefaultPluginManager(mockPluginStateStore,
2258                 Collections.<PluginLoader>singletonList(mockPluginLoader), moduleDescriptorFactory, pluginEventManager);
2259 
2260         when(mockPluginStateStore.load()).thenReturn(new DefaultPluginPersistentState());
2261         when(mockPluginStateStore.load()).thenReturn(new DefaultPluginPersistentState());
2262         when(mockPluginStateStore.load()).thenReturn(new DefaultPluginPersistentState());
2263         when(mockDescriptorParser.getKey()).thenReturn("test");
2264         when(mockPluginLoader.loadAllPlugins(moduleDescriptorFactory)).thenReturn((Iterable) Collections.emptyList());
2265         when(mockPluginLoader.supportsAddition()).thenReturn(true);
2266         when(mockPluginLoader.loadFoundPlugins(moduleDescriptorFactory)).thenReturn(Collections.singletonList(plugin));
2267         when(mockPluginLoader.canLoad(pluginArtifact)).thenReturn("test");
2268         when(plugin.getKey()).thenReturn("test");
2269         when(plugin.getModuleDescriptors()).thenReturn((Collection) new ArrayList<Object>());
2270         when(plugin.getModuleDescriptors()).thenReturn((Collection) new ArrayList<Object>());
2271         when(plugin.isEnabledByDefault()).thenReturn(true);
2272 
2273         when(plugin.isEnabledByDefault()).thenReturn(true);
2274         when(plugin.isEnabled()).thenReturn(true);
2275         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
2276         when(plugin.hasAllPermissions()).thenReturn(true);
2277         when(plugin.getActivePermissions()).thenReturn(ImmutableSet.of(Permissions.ALL_PERMISSIONS));
2278 
2279         pluginManager.setPluginInstaller(mockRepository);
2280         pluginManager.init();
2281         final PassListener enabledListener = new PassListener(PluginEnabledEvent.class);
2282         pluginEventManager.register(enabledListener);
2283         pluginManager.installPlugin(pluginArtifact);
2284 
2285         assertEquals(plugin, pluginManager.getPlugin("test"));
2286         assertTrue(pluginManager.isPluginEnabled("test"));
2287 
2288 //        plugin.verify();
2289 //        mockRepository.verify();
2290 //        pluginArtifact.verify();
2291 //        mockDescriptorParser.verify();
2292 //        mockDescriptorParserFactory.verify();
2293 //        mockPluginLoader.verify();
2294 //        mockPluginStateStore.verify();
2295         enabledListener.assertCalled();
2296     }
2297 
2298     @Test
2299     public void testInstallPluginsWithOne()
2300     {
2301         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
2302         when(loader.isDynamicPluginLoader()).thenReturn(true);
2303 
2304         ModuleDescriptorFactory descriptorFactory = mock(ModuleDescriptorFactory.class);
2305         PluginEventManager eventManager = mock(PluginEventManager.class);
2306         PluginInstaller installer = mock(PluginInstaller.class);
2307         DefaultPluginManager pm = new DefaultPluginManager(new MemoryPluginPersistentStateStore(), Collections.<PluginLoader>singletonList(loader), descriptorFactory, eventManager);
2308         pm.setPluginInstaller(installer);
2309         PluginArtifact artifact = mock(PluginArtifact.class);
2310         Plugin plugin = mock(Plugin.class);
2311         when(loader.canLoad(artifact)).thenReturn("foo");
2312         when(loader.loadFoundPlugins(descriptorFactory)).thenReturn(Arrays.asList(plugin));
2313 
2314         pm.installPlugins(artifact);
2315 
2316         verify(loader).canLoad(artifact);
2317         verify(installer).installPlugin("foo", artifact);
2318     }
2319 
2320     @Test
2321     public void testInstallPluginsWithTwo()
2322     {
2323         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
2324         when(loader.isDynamicPluginLoader()).thenReturn(true);
2325 
2326         ModuleDescriptorFactory descriptorFactory = mock(ModuleDescriptorFactory.class);
2327         PluginEventManager eventManager = mock(PluginEventManager.class);
2328         PluginInstaller installer = mock(PluginInstaller.class);
2329         DefaultPluginManager pm = new DefaultPluginManager(new MemoryPluginPersistentStateStore(), Collections.<PluginLoader>singletonList(loader), descriptorFactory, eventManager);
2330         pm.setPluginInstaller(installer);
2331         PluginArtifact artifactA = mock(PluginArtifact.class);
2332         Plugin pluginA = mock(Plugin.class);
2333         when(loader.canLoad(artifactA)).thenReturn("a");
2334         PluginArtifact artifactB = mock(PluginArtifact.class);
2335         Plugin pluginB = mock(Plugin.class);
2336         when(loader.canLoad(artifactB)).thenReturn("b");
2337 
2338         when(loader.loadFoundPlugins(descriptorFactory)).thenReturn(Arrays.asList(pluginA, pluginB));
2339 
2340         pm.installPlugins(artifactA, artifactB);
2341 
2342         verify(loader).canLoad(artifactA);
2343         verify(loader).canLoad(artifactB);
2344         verify(installer).installPlugin("a", artifactA);
2345         verify(installer).installPlugin("b", artifactB);
2346     }
2347 
2348     @Test
2349     public void testInstallPluginsWithTwoButOneFailsValidation()
2350     {
2351         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
2352         when(loader.isDynamicPluginLoader()).thenReturn(true);
2353 
2354         ModuleDescriptorFactory descriptorFactory = mock(ModuleDescriptorFactory.class);
2355         PluginEventManager eventManager = mock(PluginEventManager.class);
2356         PluginInstaller installer = mock(PluginInstaller.class);
2357         DefaultPluginManager pm = new DefaultPluginManager(new MemoryPluginPersistentStateStore(), Collections.<PluginLoader>singletonList(loader), descriptorFactory, eventManager);
2358         pm.setPluginInstaller(installer);
2359         PluginArtifact artifactA = mock(PluginArtifact.class);
2360         Plugin pluginA = mock(Plugin.class);
2361         when(loader.canLoad(artifactA)).thenReturn("a");
2362         PluginArtifact artifactB = mock(PluginArtifact.class);
2363         Plugin pluginB = mock(Plugin.class);
2364         when(loader.canLoad(artifactB)).thenReturn(null);
2365 
2366         when(loader.loadFoundPlugins(descriptorFactory)).thenReturn(Arrays.asList(pluginA, pluginB));
2367 
2368         try
2369         {
2370             pm.installPlugins(artifactA, artifactB);
2371             fail("Should have not installed plugins");
2372         }
2373         catch (PluginParseException ex)
2374         {
2375             // this is good
2376         }
2377 
2378         verify(loader).canLoad(artifactA);
2379         verify(loader).canLoad(artifactB);
2380         verify(installer, never()).installPlugin("a", artifactA);
2381         verify(installer, never()).installPlugin("b", artifactB);
2382     }
2383 
2384     @Test
2385     public void testInstallPluginsWithTwoButOneFailsValidationWithException()
2386     {
2387         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
2388         when(loader.isDynamicPluginLoader()).thenReturn(true);
2389 
2390         ModuleDescriptorFactory descriptorFactory = mock(ModuleDescriptorFactory.class);
2391         PluginEventManager eventManager = mock(PluginEventManager.class);
2392         PluginInstaller installer = mock(PluginInstaller.class);
2393         DefaultPluginManager pm = new DefaultPluginManager(new MemoryPluginPersistentStateStore(), Collections.<PluginLoader>singletonList(loader), descriptorFactory, eventManager);
2394         pm.setPluginInstaller(installer);
2395         PluginArtifact artifactA = mock(PluginArtifact.class);
2396         Plugin pluginA = mock(Plugin.class);
2397         when(loader.canLoad(artifactA)).thenReturn("a");
2398         PluginArtifact artifactB = mock(PluginArtifact.class);
2399         Plugin pluginB = mock(Plugin.class);
2400         doThrow(new PluginParseException()).when(loader).canLoad(artifactB);
2401 
2402         when(loader.loadFoundPlugins(descriptorFactory)).thenReturn(Arrays.asList(pluginA, pluginB));
2403 
2404         try
2405         {
2406             pm.installPlugins(artifactA, artifactB);
2407             fail("Should have not installed plugins");
2408         }
2409         catch (PluginParseException ex)
2410         {
2411             // this is good
2412         }
2413 
2414         verify(loader).canLoad(artifactA);
2415         verify(loader).canLoad(artifactB);
2416         verify(installer, never()).installPlugin("a", artifactA);
2417         verify(installer, never()).installPlugin("b", artifactB);
2418     }
2419 
2420     Plugin mockStaticPlugin(final String pluginKey, final ModuleDescriptor<?>... descriptors)
2421     {
2422         return new StaticPlugin()
2423         {
2424             {
2425                 setPluginInformation(new PluginInformation());
2426                 setEnabledByDefault(true);
2427                 setKey(pluginKey);
2428             }
2429 
2430             @Override
2431             public Collection<ModuleDescriptor<?>> getModuleDescriptors()
2432             {
2433                 return Arrays.<ModuleDescriptor<?>>asList(descriptors);
2434             }
2435 
2436             @Override
2437             public ModuleDescriptor<Object> getModuleDescriptor(final String moduleKey)
2438             {
2439                 for (ModuleDescriptor desc : descriptors)
2440                 {
2441                     if (desc.getKey().equals(moduleKey))
2442                         return desc;
2443                 }
2444                 return null;
2445             }
2446         };
2447     }
2448     
2449     @Test
2450     public void testInstallTwoPluginsButOneFailsToEnableAModuleAndThenFailsToDisableAModule()
2451     {
2452         final PluginLoader mockPluginLoader = mock(PluginLoader.class);
2453         final ModuleDescriptor<Object> failEnableModuleDescriptor = mockFailingModuleDescriptor("foo:bar", FailureMode.FAIL_TO_ENABLE);
2454         final ModuleDescriptor<Object> failDisableModuleDescriptor = mockFailingModuleDescriptor("foo:buzz", FailureMode.FAIL_TO_DISABLE);
2455 
2456         Plugin badPlugin = mockStaticPlugin("foo", failDisableModuleDescriptor, failEnableModuleDescriptor);
2457 
2458         final AbstractModuleDescriptor<?> goodModuleDescriptor = mock(AbstractModuleDescriptor.class);
2459         when(goodModuleDescriptor.getKey()).thenReturn("baz");
2460         when(goodModuleDescriptor.getCompleteKey()).thenReturn("good:baz");
2461         when(goodModuleDescriptor.isEnabledByDefault()).thenReturn(true);
2462         Plugin goodPlugin = mockStaticPlugin("good", goodModuleDescriptor);
2463 
2464         when(mockPluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Lists.newArrayList(badPlugin, goodPlugin));
2465 
2466         manager = newDefaultPluginManager(mockPluginLoader);
2467         manager.init();
2468 
2469         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
2470         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
2471         verify(goodModuleDescriptor).enabled();
2472     }
2473 
2474     private <T> void checkResources(final PluginAccessor manager, final boolean canGetGlobal, final boolean canGetModule) throws IOException
2475     {
2476         InputStream is = manager.getDynamicResourceAsStream("icon.gif");
2477         assertEquals(canGetGlobal, is != null);
2478         IOUtils.closeQuietly(is);
2479         is = manager.getDynamicResourceAsStream("bear/paddington.vm");
2480         assertEquals(canGetModule, is != null);
2481         IOUtils.closeQuietly(is);
2482     }
2483 
2484     private <T> void checkClasses(final PluginAccessor manager, final boolean canGet)
2485     {
2486         try
2487         {
2488             manager.getDynamicPluginClass("com.atlassian.plugin.mock.MockPaddington");
2489             if (!canGet)
2490             {
2491                 fail("Class in plugin was successfully loaded");
2492             }
2493         }
2494         catch (final ClassNotFoundException e)
2495         {
2496             if (canGet)
2497             {
2498                 fail(e.getMessage());
2499             }
2500         }
2501     }
2502 
2503     @Test
2504     public void testAddPluginsThatThrowExceptionOnEnabled() throws Exception
2505     {
2506         final Plugin plugin = new CannotEnablePlugin();
2507 
2508         manager = newDefaultPluginManager();
2509         manager.addPlugins(null, Arrays.asList(plugin));
2510 
2511         assertFalse(plugin.getPluginState() == PluginState.ENABLED);
2512     }
2513 
2514     @Test
2515     public void testUninstallPluginClearsState() throws IOException
2516     {
2517         createFillAndCleanTempPluginDirectory();
2518 
2519         final DefaultPluginManager manager = makeClassLoadingPluginManager();
2520 
2521         checkClasses(manager, true);
2522         final Plugin plugin = manager.getPlugin("test.atlassian.plugin.classloaded");
2523 
2524         final ModuleDescriptor<?> module = plugin.getModuleDescriptor("paddington");
2525         assertTrue(manager.isPluginModuleEnabled(module.getCompleteKey()));
2526         manager.disablePluginModule(module.getCompleteKey());
2527         assertFalse(manager.isPluginModuleEnabled(module.getCompleteKey()));
2528         manager.uninstall(plugin);
2529         assertFalse(manager.isPluginModuleEnabled(module.getCompleteKey()));
2530         assertTrue(pluginStateStore.load().getPluginStateMap(plugin).isEmpty());
2531     }
2532 
2533     @Test
2534     public void testCannotInitTwice() throws PluginParseException
2535     {
2536         manager = newDefaultPluginManager();
2537         manager.init();
2538         try
2539         {
2540             manager.init();
2541             fail("IllegalStateException expected");
2542         }
2543         catch (final IllegalStateException expected)
2544         {
2545         }
2546     }
2547 
2548     @Test
2549     public void testCannotShutdownTwice() throws PluginParseException
2550     {
2551         manager = newDefaultPluginManager();
2552         manager.init();
2553         manager.shutdown();
2554         try
2555         {
2556             manager.shutdown();
2557             fail("IllegalStateException expected");
2558         }
2559         catch (final IllegalStateException expected)
2560         {
2561         }
2562     }
2563 
2564     @Test
2565     public void testGetPluginWithNullKey()
2566     {
2567         manager = newDefaultPluginManager();
2568         manager.init();
2569         try
2570         {
2571             getPluginAccessor().getPlugin(null);
2572             fail();
2573         }
2574         catch (IllegalArgumentException ex)
2575         {
2576             // test passed
2577         }
2578     }
2579 
2580     @Test
2581     public void testShutdownHandlesException()
2582     {
2583         final ThingsAreWrongListener listener = new ThingsAreWrongListener();
2584         pluginEventManager.register(listener);
2585 
2586         manager = newDefaultPluginManager();
2587         manager.init();
2588         try
2589         {
2590             //this should not throw an exception
2591             manager.shutdown();
2592         }
2593         catch (Exception e)
2594         {
2595             fail("Should not have thrown an exception!");
2596         }
2597         assertTrue(listener.isCalled());
2598     }
2599 
2600     private void writeToFile(String file, String line) throws IOException, URISyntaxException
2601     {
2602         final String resourceName = ClasspathFilePluginMetadata.class.getPackage().getName().replace(".", "/") + "/" + file;
2603         URL resource = getClass().getClassLoader().getResource(resourceName);
2604 
2605         FileOutputStream fout = new FileOutputStream(new File(resource.toURI()), false);
2606         fout.write(line.getBytes(), 0, line.length());
2607         fout.close();
2608 
2609     }
2610 
2611     @Test
2612     public void testExceptionOnRequiredPluginNotEnabling() throws Exception
2613     {
2614         try
2615         {
2616             writeToFile("application-required-modules.txt", "foo.required:bar");
2617             writeToFile("application-required-plugins.txt", "foo.required");
2618 
2619             final PluginLoader mockPluginLoader = mock(PluginLoader.class);
2620             final ModuleDescriptor<Object> badModuleDescriptor = mockFailingModuleDescriptor("foo.required:bar", FailureMode.FAIL_TO_ENABLE);
2621 
2622             final AbstractModuleDescriptor goodModuleDescriptor = mock(AbstractModuleDescriptor.class);
2623             when(goodModuleDescriptor.getKey()).thenReturn("baz");
2624             when(goodModuleDescriptor.getCompleteKey()).thenReturn("foo.required:baz");
2625 
2626             Plugin plugin = mockStaticPlugin("foo.required", goodModuleDescriptor, badModuleDescriptor);
2627 
2628             when(mockPluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(Collections.singletonList(plugin));
2629 
2630             manager = newDefaultPluginManager(mockPluginLoader);
2631             try
2632             {
2633                 manager.init();
2634             }
2635             catch(PluginException e)
2636             {
2637                 // expected
2638                 assertEquals("Unable to validate required plugins or modules", e.getMessage());
2639                 return;
2640             }
2641             fail("A PluginException is expected when trying to initialize the plugins system with required plugins that do not load.");
2642         }
2643         finally
2644         {
2645             // remove references from required files
2646             writeToFile("application-required-modules.txt", "");
2647             writeToFile("application-required-plugins.txt", "");
2648         }
2649     }
2650 
2651     public static class ThingsAreWrongListener
2652     {
2653         private volatile boolean called = false;
2654 
2655         @PluginEventListener
2656         public void onFrameworkShutdown(final PluginFrameworkShutdownEvent event)
2657         {
2658             called = true;
2659             throw new NullPointerException("AAAH!");
2660         }
2661 
2662         public boolean isCalled()
2663         {
2664             return called;
2665         }
2666     }
2667 
2668     public Plugin createPluginWithVersion(final String version)
2669     {
2670         final Plugin p = new StaticPlugin();
2671         p.setKey("test.default.plugin");
2672         final PluginInformation pInfo = p.getPluginInformation();
2673         pInfo.setVersion(version);
2674         return p;
2675     }
2676 
2677     /**
2678      * Dummy plugin loader that reports that removal is supported and returns plugins that report that they can
2679      * be uninstalled.
2680      */
2681     private static class SinglePluginLoaderWithRemoval extends SinglePluginLoader
2682     {
2683         public SinglePluginLoaderWithRemoval(final String resource)
2684         {
2685             super(resource);
2686         }
2687 
2688         public boolean supportsRemoval()
2689         {
2690 
2691             return true;
2692         }
2693 
2694         public void removePlugin(final Plugin plugin) throws PluginException
2695         {
2696             plugins = Collections.emptyList();
2697         }
2698 
2699         protected StaticPlugin getNewPlugin()
2700         {
2701             return new StaticPlugin()
2702             {
2703                 public boolean isUninstallable()
2704                 {
2705                     return true;
2706                 }
2707             };
2708         }
2709     }
2710 
2711     class NothingModuleDescriptor extends MockUnusedModuleDescriptor
2712     {
2713     }
2714 
2715     @RequiresRestart
2716     public static class RequiresRestartModuleDescriptor extends MockUnusedModuleDescriptor
2717     {
2718     }
2719 
2720     // A subclass of a module descriptor that @RequiresRestart; should inherit the annotation
2721     public static class RequiresRestartSubclassModuleDescriptor extends RequiresRestartModuleDescriptor
2722     {
2723     }
2724 
2725     private class MultiplePluginLoader implements PluginLoader
2726     {
2727         private final String[] descriptorPaths;
2728 
2729         public MultiplePluginLoader(final String... descriptorPaths)
2730         {
2731             this.descriptorPaths = descriptorPaths;
2732         }
2733 
2734         public Iterable<Plugin> loadAllPlugins(final ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
2735         {
2736             final ImmutableList.Builder<Plugin> result = ImmutableList.builder();
2737             for (final String path : descriptorPaths)
2738             {
2739                 final SinglePluginLoader loader = new SinglePluginLoader(path);
2740                 result.addAll(loader.loadAllPlugins(moduleDescriptorFactory));
2741             }
2742             return result.build();
2743         }
2744 
2745         public boolean supportsAddition()
2746         {
2747             return false;
2748         }
2749 
2750         public boolean supportsRemoval()
2751         {
2752             return false;
2753         }
2754 
2755         public Iterable<Plugin> loadFoundPlugins(final ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
2756         {
2757             throw new UnsupportedOperationException("This PluginLoader does not support addition.");
2758         }
2759 
2760         public void removePlugin(final Plugin plugin) throws PluginException
2761         {
2762             throw new UnsupportedOperationException("This PluginLoader does not support addition.");
2763         }
2764 
2765         @Override
2766         public boolean isDynamicPluginLoader()
2767         {
2768             return false;
2769         }
2770     }
2771 
2772     private static class DynamicSinglePluginLoader extends SinglePluginLoader implements PluginLoader, DynamicPluginLoader
2773     {
2774         private final AtomicBoolean canLoad = new AtomicBoolean(false);
2775 
2776         private final String key;
2777 
2778         public DynamicSinglePluginLoader(final String key, final String resource)
2779         {
2780             super(resource);
2781             this.key = key;
2782         }
2783 
2784         @Override
2785         public boolean isDynamicPluginLoader()
2786         {
2787             return true;
2788         }
2789 
2790         public String canLoad(final PluginArtifact pluginArtifact) throws PluginParseException
2791         {
2792             return canLoad.get() ? key : null;
2793         }
2794 
2795         public boolean supportsAddition()
2796         {
2797             return true;
2798         }
2799 
2800         @Override
2801         public Iterable<Plugin> loadAllPlugins(ModuleDescriptorFactory moduleDescriptorFactory)
2802         {
2803             if (canLoad.get())
2804             {
2805                 return super.loadAllPlugins(moduleDescriptorFactory);
2806             }
2807             else
2808             {
2809                 return ImmutableList.of();
2810             }
2811         }
2812 
2813         @Override
2814         public Iterable<Plugin> loadFoundPlugins(final ModuleDescriptorFactory moduleDescriptorFactory)
2815         {
2816             if (canLoad.get())
2817             {
2818                 return super.loadAllPlugins(moduleDescriptorFactory);
2819             }
2820             else
2821             {
2822                 return ImmutableList.of();
2823             }
2824         }
2825     }
2826 
2827     private static class CannotEnablePlugin extends StaticPlugin
2828     {
2829         public CannotEnablePlugin()
2830         {
2831             setKey("foo");
2832         }
2833 
2834         @Override
2835         protected PluginState enableInternal()
2836         {
2837             throw new RuntimeException("boo");
2838         }
2839 
2840         public void disabled()
2841         {
2842         }
2843     }
2844 
2845     public static class PluginModuleEnabledListener
2846     {
2847         public volatile boolean called;
2848         @PluginEventListener
2849         public void onEnable(PluginModuleEnabledEvent event)
2850         {
2851             called = true;
2852         }
2853     }
2854 
2855     public static class PluginModuleDisabledListener
2856     {
2857         public volatile boolean called;
2858         @PluginEventListener
2859         public void onDisable(PluginModuleDisabledEvent event)
2860         {
2861             called = true;
2862         }
2863     }
2864 
2865     public static class PluginDisabledListener
2866     {
2867         public volatile boolean called;
2868         @PluginEventListener
2869         public void onDisable(PluginDisabledEvent event)
2870         {
2871             called = true;
2872         }
2873     }
2874 }