View Javadoc

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