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