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.Map;
15  import java.util.concurrent.atomic.AtomicBoolean;
16  
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.ModuleCompleteKey;
22  import com.atlassian.plugin.ModuleDescriptor;
23  import com.atlassian.plugin.ModuleDescriptorFactory;
24  import com.atlassian.plugin.Permissions;
25  import com.atlassian.plugin.Plugin;
26  import com.atlassian.plugin.PluginAccessor;
27  import com.atlassian.plugin.PluginArtifact;
28  import com.atlassian.plugin.PluginException;
29  import com.atlassian.plugin.PluginInformation;
30  import com.atlassian.plugin.PluginInstaller;
31  import com.atlassian.plugin.PluginParseException;
32  import com.atlassian.plugin.PluginRestartState;
33  import com.atlassian.plugin.PluginState;
34  import com.atlassian.plugin.SplitStartupPluginSystemLifecycle;
35  import com.atlassian.plugin.StateAware;
36  import com.atlassian.plugin.descriptors.AbstractModuleDescriptor;
37  import com.atlassian.plugin.descriptors.MockUnusedModuleDescriptor;
38  import com.atlassian.plugin.descriptors.RequiresRestart;
39  import com.atlassian.plugin.event.NotificationException;
40  import com.atlassian.plugin.event.PluginEventListener;
41  import com.atlassian.plugin.event.PluginEventManager;
42  import com.atlassian.plugin.event.events.BeforePluginDisabledEvent;
43  import com.atlassian.plugin.event.events.BeforePluginModuleDisabledEvent;
44  import com.atlassian.plugin.event.events.PluginContainerUnavailableEvent;
45  import com.atlassian.plugin.event.events.PluginDisabledEvent;
46  import com.atlassian.plugin.event.events.PluginEnabledEvent;
47  import com.atlassian.plugin.event.events.PluginFrameworkDelayedEvent;
48  import com.atlassian.plugin.event.events.PluginFrameworkResumingEvent;
49  import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
50  import com.atlassian.plugin.event.events.PluginFrameworkShuttingDownEvent;
51  import com.atlassian.plugin.event.events.PluginFrameworkStartedEvent;
52  import com.atlassian.plugin.event.events.PluginFrameworkStartingEvent;
53  import com.atlassian.plugin.event.events.PluginModuleAvailableEvent;
54  import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
55  import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
56  import com.atlassian.plugin.event.events.PluginModuleUnavailableEvent;
57  import com.atlassian.plugin.event.events.PluginUninstalledEvent;
58  import com.atlassian.plugin.event.events.PluginUpgradedEvent;
59  import com.atlassian.plugin.event.impl.DefaultPluginEventManager;
60  import com.atlassian.plugin.event.listeners.FailListener;
61  import com.atlassian.plugin.event.listeners.PassListener;
62  import com.atlassian.plugin.exception.PluginExceptionInterception;
63  import com.atlassian.plugin.factories.LegacyDynamicPluginFactory;
64  import com.atlassian.plugin.factories.PluginFactory;
65  import com.atlassian.plugin.factories.XmlDynamicPluginFactory;
66  import com.atlassian.plugin.hostcontainer.DefaultHostContainer;
67  import com.atlassian.plugin.impl.AbstractDelegatingPlugin;
68  import com.atlassian.plugin.impl.StaticPlugin;
69  import com.atlassian.plugin.impl.UnloadablePlugin;
70  import com.atlassian.plugin.loaders.DirectoryPluginLoader;
71  import com.atlassian.plugin.loaders.DiscardablePluginLoader;
72  import com.atlassian.plugin.loaders.DynamicPluginLoader;
73  import com.atlassian.plugin.loaders.PluginLoader;
74  import com.atlassian.plugin.loaders.SinglePluginLoader;
75  import com.atlassian.plugin.loaders.classloading.DirectoryPluginLoaderUtils;
76  import com.atlassian.plugin.manager.store.DelegatingPluginPersistentStateStore;
77  import com.atlassian.plugin.manager.store.LoadOnlyPluginPersistentStateStore;
78  import com.atlassian.plugin.manager.store.MemoryPluginPersistentStateStore;
79  import com.atlassian.plugin.metadata.ClasspathFilePluginMetadata;
80  import com.atlassian.plugin.mock.MockAnimal;
81  import com.atlassian.plugin.mock.MockAnimalModuleDescriptor;
82  import com.atlassian.plugin.mock.MockBear;
83  import com.atlassian.plugin.mock.MockMineral;
84  import com.atlassian.plugin.mock.MockMineralModuleDescriptor;
85  import com.atlassian.plugin.mock.MockThing;
86  import com.atlassian.plugin.mock.MockVegetableModuleDescriptor;
87  import com.atlassian.plugin.mock.MockVegetableSubclassModuleDescriptor;
88  import com.atlassian.plugin.module.ModuleFactory;
89  import com.atlassian.plugin.parsers.DescriptorParser;
90  import com.atlassian.plugin.parsers.DescriptorParserFactory;
91  import com.atlassian.plugin.predicate.ModuleDescriptorPredicate;
92  import com.atlassian.plugin.predicate.PluginPredicate;
93  import com.atlassian.plugin.repositories.FilePluginInstaller;
94  import com.atlassian.plugin.test.CapturedLogging;
95  import com.atlassian.plugin.test.PluginJarBuilder;
96  
97  import com.google.common.base.Function;
98  import com.google.common.base.Predicate;
99  import com.google.common.base.Predicates;
100 import com.google.common.collect.ImmutableList;
101 import com.google.common.collect.ImmutableMap;
102 import com.google.common.collect.ImmutableSet;
103 import com.google.common.collect.Iterables;
104 import com.google.common.collect.Lists;
105 
106 import org.apache.commons.io.FileUtils;
107 import org.apache.commons.io.IOUtils;
108 import org.hamcrest.Description;
109 import org.hamcrest.Matcher;
110 import org.hamcrest.TypeSafeMatcher;
111 import org.junit.After;
112 import org.junit.Before;
113 import org.junit.Rule;
114 import org.junit.Test;
115 import org.junit.contrib.java.lang.system.RestoreSystemProperties;
116 import org.junit.rules.ExpectedException;
117 import org.mockito.ArgumentCaptor;
118 import org.mockito.invocation.InvocationOnMock;
119 import org.mockito.stubbing.Answer;
120 
121 import static com.atlassian.plugin.loaders.classloading.DirectoryPluginLoaderUtils.PADDINGTON_JAR;
122 import static com.atlassian.plugin.manager.DefaultPluginManager.getLateStartupEnableRetryProperty;
123 import static com.atlassian.plugin.manager.DefaultPluginManager.getStartupOverrideFileProperty;
124 import static com.atlassian.plugin.test.CapturedLogging.didLogWarn;
125 import static com.atlassian.plugin.test.PluginTestUtils.createTempDirectory;
126 import static com.google.common.collect.ImmutableList.copyOf;
127 import static com.google.common.collect.Iterables.filter;
128 import static com.google.common.collect.Iterables.get;
129 import static com.google.common.collect.Iterables.getLast;
130 import static com.google.common.collect.Iterables.indexOf;
131 import static java.util.Collections.singleton;
132 import static org.apache.commons.io.FileUtils.deleteQuietly;
133 import static org.apache.commons.io.FileUtils.writeLines;
134 import static org.hamcrest.MatcherAssert.assertThat;
135 import static org.hamcrest.Matchers.contains;
136 import static org.hamcrest.Matchers.containsInAnyOrder;
137 import static org.hamcrest.Matchers.empty;
138 import static org.hamcrest.Matchers.greaterThan;
139 import static org.hamcrest.Matchers.hasSize;
140 import static org.hamcrest.Matchers.instanceOf;
141 import static org.hamcrest.Matchers.is;
142 import static org.hamcrest.Matchers.lessThan;
143 import static org.hamcrest.Matchers.not;
144 import static org.hamcrest.Matchers.nullValue;
145 import static org.junit.Assert.assertEquals;
146 import static org.junit.Assert.assertFalse;
147 import static org.junit.Assert.assertNotNull;
148 import static org.junit.Assert.assertNull;
149 import static org.junit.Assert.assertTrue;
150 import static org.junit.Assert.fail;
151 import static org.mockito.Matchers.any;
152 import static org.mockito.Matchers.anyString;
153 import static org.mockito.Matchers.isA;
154 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
155 import static org.mockito.Mockito.RETURNS_MOCKS;
156 import static org.mockito.Mockito.doAnswer;
157 import static org.mockito.Mockito.doNothing;
158 import static org.mockito.Mockito.doThrow;
159 import static org.mockito.Mockito.mock;
160 import static org.mockito.Mockito.never;
161 import static org.mockito.Mockito.reset;
162 import static org.mockito.Mockito.times;
163 import static org.mockito.Mockito.verify;
164 import static org.mockito.Mockito.when;
165 import static org.mockito.Mockito.withSettings;
166 
167 /**
168  * Unit and low level integration tests for TestDefaultPluginManager.
169  *
170  * If you're tempted to try to push mocks further through this, be aware that there are subclasses
171  * which need to wrap components and which react to events sent during processing. That is, these
172  * tests are also testing to some extent integration between DefaultPluginManager and
173  * DefaultPluginEventManager and other related classes.
174  */
175 public class TestDefaultPluginManager
176 {
177     @Rule
178     public CapturedLogging capturedLogging = new CapturedLogging(DefaultPluginManager.class);
179 
180     @Rule
181     public ExpectedException expectedException = ExpectedException.none();
182 
183     @Rule
184     public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(
185             getStartupOverrideFileProperty(),
186             getLateStartupEnableRetryProperty());
187 
188     /**
189      * the object being tested
190      */
191     protected DefaultPluginManager manager;
192 
193     private PluginPersistentStateStore pluginStateStore;
194     private DefaultModuleDescriptorFactory moduleDescriptorFactory; // we should be able to use the interface here?
195 
196     private DirectoryPluginLoader directoryPluginLoader;
197     protected PluginEventManager pluginEventManager;
198     private File pluginsDirectory;
199     private File pluginsTestDir;
200     private File temporaryDirectory;
201     private File startupOverrideFile;
202 
203     private void createFillAndCleanTempPluginDirectory() throws IOException
204     {
205         final DirectoryPluginLoaderUtils.ScannerDirectories directories = DirectoryPluginLoaderUtils.createFillAndCleanTempPluginDirectory();
206         pluginsDirectory = directories.pluginsDirectory;
207         pluginsTestDir = directories.pluginsTestDir;
208     }
209 
210     @Before
211     public void setUp() throws Exception
212     {
213         pluginEventManager = new DefaultPluginEventManager();
214 
215         pluginStateStore = new MemoryPluginPersistentStateStore();
216         moduleDescriptorFactory = new DefaultModuleDescriptorFactory(new DefaultHostContainer());
217         temporaryDirectory = createTempDirectory(TestDefaultPluginManager.class);
218         startupOverrideFile = new File(temporaryDirectory, "startupOverride.properties");
219     }
220 
221     @After
222     public void tearDown() throws Exception
223     {
224         manager = null;
225         moduleDescriptorFactory = null;
226         pluginStateStore = null;
227 
228         if (directoryPluginLoader != null)
229         {
230             directoryPluginLoader = null;
231         }
232         deleteQuietly(temporaryDirectory);
233     }
234 
235     protected DefaultPluginManager newDefaultPluginManager(PluginLoader... pluginLoaders)
236     {
237         manager = new DefaultPluginManager(pluginStateStore, copyOf(pluginLoaders), moduleDescriptorFactory, pluginEventManager, true);
238         return manager;
239     }
240 
241     protected PluginAccessor getPluginAccessor()
242     {
243         return manager;
244     }
245 
246     @Test
247     public void testRetrievePlugins() throws PluginParseException
248     {
249         manager = newDefaultPluginManager(
250                 new SinglePluginLoader("test-atlassian-plugin.xml"),
251                 new SinglePluginLoader("test-disabled-plugin.xml"));
252 
253         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
254         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
255         manager.init();
256 
257         assertThat(manager.getPlugins(), hasSize(2));
258         assertThat(manager.getEnabledPlugins(), hasSize(1));
259         manager.enablePlugin("test.disabled.plugin");
260         assertThat(manager.getEnabledPlugins(), hasSize(2));
261     }
262 
263     private enum FailureMode
264     {
265         FAIL_TO_ENABLE,
266         FAIL_TO_DISABLE
267     }
268 
269     private ModuleDescriptor<Object> mockFailingModuleDescriptor(final String completeKey, final FailureMode... failureModes)
270     {
271         return new AbstractModuleDescriptor<Object>(ModuleFactory.LEGACY_MODULE_FACTORY)
272         {
273             @Override
274             public String getKey()
275             {
276                 return completeKey.substring(completeKey.lastIndexOf(":") + 1, completeKey.length());
277             }
278 
279             @Override
280             public String getCompleteKey()
281             {
282                 return completeKey;
283             }
284 
285             @Override
286             public void enabled()
287             {
288                 if (Lists.newArrayList(failureModes).contains(FailureMode.FAIL_TO_ENABLE))
289                     throw new IllegalArgumentException("Cannot enable");
290             }
291 
292             @Override
293             public void disabled()
294             {
295                 if (Lists.newArrayList(failureModes).contains(FailureMode.FAIL_TO_DISABLE))
296                     throw new IllegalArgumentException("Cannot disable");
297             }
298 
299             @Override
300             public Object getModule()
301             {
302                 return null;
303             }
304         };
305     }
306     
307     @Test
308     public void testEnableModuleFailed() throws PluginParseException
309     {
310         final PluginLoader mockPluginLoader = mock(PluginLoader.class);
311         final ModuleDescriptor<Object> badModuleDescriptor = mockFailingModuleDescriptor("foo:bar", FailureMode.FAIL_TO_ENABLE);
312 
313         final AbstractModuleDescriptor goodModuleDescriptor = mock(AbstractModuleDescriptor.class);
314         when(goodModuleDescriptor.getKey()).thenReturn("baz");
315         when(goodModuleDescriptor.getCompleteKey()).thenReturn("foo:baz");
316         when(goodModuleDescriptor.isEnabledByDefault()).thenReturn(true);
317 
318         Plugin plugin = mockStaticPlugin("foo", goodModuleDescriptor, badModuleDescriptor);
319         
320         when(mockPluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Collections.singletonList(plugin));
321 
322         pluginEventManager.register(new FailListener(PluginEnabledEvent.class));
323 
324         MyModuleDisabledListener listener = new MyModuleDisabledListener(goodModuleDescriptor);
325         pluginEventManager.register(listener);
326 
327         manager = newDefaultPluginManager(mockPluginLoader);
328         manager.init();
329 
330         assertThat(getPluginAccessor().getPlugins(), hasSize(1));
331         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(0));
332         plugin = getPluginAccessor().getPlugin("foo");
333         assertFalse(plugin.getPluginState() == PluginState.ENABLED);
334         assertTrue(plugin instanceof UnloadablePlugin);
335         assertTrue(listener.isCalled());
336     }
337 
338     public static class MyModuleDisabledListener
339     {
340         private final ModuleDescriptor goodModuleDescriptor;
341         private volatile boolean disableCalled = false;
342 
343         public MyModuleDisabledListener(ModuleDescriptor goodModuleDescriptor)
344         {
345             this.goodModuleDescriptor = goodModuleDescriptor;
346         }
347 
348         @PluginEventListener
349         public void onDisable(PluginModuleDisabledEvent evt)
350         {
351             if (evt.getModule().equals(goodModuleDescriptor))
352             {
353                 disableCalled = true;
354             }
355         }
356 
357         public boolean isCalled()
358         {
359             return disableCalled;
360         }
361     }
362 
363     @Test
364     public void testEnabledModuleOutOfSyncWithPlugin() throws PluginParseException
365     {
366         final PluginLoader mockPluginLoader = mock(PluginLoader.class);
367         Plugin plugin = new StaticPlugin();
368         plugin.setKey("foo");
369         plugin.setEnabledByDefault(true);
370         plugin.setPluginInformation(new PluginInformation());
371 
372         when(mockPluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Collections.singletonList(plugin));
373 
374         manager = newDefaultPluginManager(mockPluginLoader);
375         manager.init();
376 
377         assertThat(getPluginAccessor().getPlugins(), hasSize(1));
378         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
379         plugin = getPluginAccessor().getPlugin("foo");
380         assertTrue(plugin.getPluginState() == PluginState.ENABLED);
381         assertTrue(getPluginAccessor().isPluginEnabled("foo"));
382         plugin.disable();
383         assertFalse(plugin.getPluginState() == PluginState.ENABLED);
384         assertFalse(getPluginAccessor().isPluginEnabled("foo"));
385     }
386 
387     @Test
388     public void testDisablePluginModuleWithCannotDisableAnnotation()
389     {
390         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
391         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
392         moduleDescriptorFactory.addModuleDescriptor("bullshit", MockUnusedModuleDescriptor.class);
393         moduleDescriptorFactory.addModuleDescriptor("vegetable", MockVegetableModuleDescriptor.class);
394 
395         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
396         manager.init();
397 
398         final String pluginKey = "test.atlassian.plugin";
399         final String disablableModuleKey = pluginKey + ":bear";
400         final String moduleKey = pluginKey + ":veg";
401 
402         // First, make sure we can disable the bear module
403         manager.disablePluginModule(disablableModuleKey);
404         assertNull(getPluginAccessor().getEnabledPluginModule(disablableModuleKey));
405 
406         // Now, make sure we can't disable the veg module
407         manager.disablePluginModule(moduleKey);
408         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
409     }
410 
411     @Test
412     public void testDisablePluginModuleWithCannotDisableAnnotationInSuperclass()
413     {
414         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
415         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
416         moduleDescriptorFactory.addModuleDescriptor("bullshit", MockUnusedModuleDescriptor.class);
417         moduleDescriptorFactory.addModuleDescriptor("vegetable", MockVegetableModuleDescriptor.class);
418         moduleDescriptorFactory.addModuleDescriptor("vegetableSubclass", MockVegetableSubclassModuleDescriptor.class);
419 
420         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
421         manager.init();
422 
423         final String pluginKey = "test.atlassian.plugin";
424         final String disablableModuleKey = pluginKey + ":bear";
425         final String moduleKey = pluginKey + ":vegSubclass";
426 
427         // First, make sure we can disable the bear module
428         manager.disablePluginModule(disablableModuleKey);
429         assertNull(getPluginAccessor().getEnabledPluginModule(disablableModuleKey));
430 
431         // Now, make sure we can't disable the vegSubclass module
432         manager.disablePluginModule(moduleKey);
433         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
434     }
435 
436     @Test
437     public void testEnabledDisabledRetrieval() throws PluginParseException
438     {
439         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
440         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
441         moduleDescriptorFactory.addModuleDescriptor("bullshit", MockUnusedModuleDescriptor.class);
442         moduleDescriptorFactory.addModuleDescriptor("vegetable", MockVegetableModuleDescriptor.class);
443 
444         final PassListener enabledListener = new PassListener(PluginEnabledEvent.class);
445         final PassListener disabledListener = new PassListener(PluginDisabledEvent.class);
446         pluginEventManager.register(enabledListener);
447         pluginEventManager.register(disabledListener);
448 
449         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
450         manager.init();
451 
452         // check non existent plugins don't show
453         assertNull(getPluginAccessor().getPlugin("bull:shit"));
454         assertNull(getPluginAccessor().getEnabledPlugin("bull:shit"));
455         assertNull(getPluginAccessor().getPluginModule("bull:shit"));
456         assertNull(getPluginAccessor().getEnabledPluginModule("bull:shit"));
457         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByClass(NothingModuleDescriptor.class).isEmpty());
458         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByType("bullshit").isEmpty());
459 
460         final String pluginKey = "test.atlassian.plugin";
461         final String moduleKey = pluginKey + ":bear";
462 
463         // retrieve everything when enabled
464         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
465         assertNotNull(getPluginAccessor().getEnabledPlugin(pluginKey));
466         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
467         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
468         assertNull(getPluginAccessor().getEnabledPluginModule(pluginKey + ":shit"));
469         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
470         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByType("animal").isEmpty());
471         assertFalse(getPluginAccessor().getEnabledModulesByClass(MockBear.class).isEmpty());
472         assertEquals(new MockBear(), getPluginAccessor().getEnabledModulesByClass(MockBear.class).get(0));
473         enabledListener.assertCalled();
474 
475         // now only retrieve via always retrieve methods
476         manager.disablePlugin(pluginKey);
477         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
478         assertNull(getPluginAccessor().getEnabledPlugin(pluginKey));
479         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
480         assertNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
481         assertTrue(getPluginAccessor().getEnabledModulesByClass(com.atlassian.plugin.mock.MockBear.class).isEmpty());
482         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
483         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByType("animal").isEmpty());
484         disabledListener.assertCalled();
485 
486         // now enable again and check back to start
487         manager.enablePlugin(pluginKey);
488         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
489         assertNotNull(getPluginAccessor().getEnabledPlugin(pluginKey));
490         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
491         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
492         assertFalse(getPluginAccessor().getEnabledModulesByClass(com.atlassian.plugin.mock.MockBear.class).isEmpty());
493         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
494         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByType("animal").isEmpty());
495         enabledListener.assertCalled();
496 
497         // now let's disable the module, but not the plugin
498         pluginEventManager.register(new FailListener(PluginEnabledEvent.class));
499         manager.disablePluginModule(moduleKey);
500         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
501         assertNotNull(getPluginAccessor().getEnabledPlugin(pluginKey));
502         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
503         assertNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
504         assertTrue(getPluginAccessor().getEnabledModulesByClass(com.atlassian.plugin.mock.MockBear.class).isEmpty());
505         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
506         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByType("animal").isEmpty());
507 
508         // now enable the module again
509         pluginEventManager.register(new FailListener(PluginDisabledEvent.class));
510         manager.enablePluginModule(moduleKey);
511         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
512         assertNotNull(getPluginAccessor().getEnabledPlugin(pluginKey));
513         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
514         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
515         assertFalse(getPluginAccessor().getEnabledModulesByClass(com.atlassian.plugin.mock.MockBear.class).isEmpty());
516         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
517         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByType("animal").isEmpty());
518     }
519 
520     @Test
521     public void testDuplicatePluginKeysAreIgnored() throws PluginParseException
522     {
523         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
524 
525         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"), new SinglePluginLoader("test-atlassian-plugin.xml"));
526         manager.init();
527         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
528     }
529 
530     @Test
531     public void testDuplicateSnapshotVersionsAreNotLoaded() throws PluginParseException
532     {
533         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
534 
535         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-snapshot-plugin.xml"), new SinglePluginLoader("test-atlassian-snapshot-plugin.xml"));
536         manager.init();
537         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
538     }
539 
540     @Test
541     public void testChangedSnapshotVersionIsLoaded() throws PluginParseException
542     {
543         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
544 
545         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-snapshot-plugin.xml"), new SinglePluginLoader("test-atlassian-snapshot-plugin-changed-same-version.xml"));
546         manager.init();
547         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
548         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
549         assertEquals("1.1-SNAPSHOT", plugin.getPluginInformation().getVersion());
550         assertEquals("This plugin descriptor has been changed!", plugin.getPluginInformation().getDescription());
551     }
552 
553     @Test
554     public void testLoadOlderDuplicatePlugin()
555     {
556         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
557         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
558 
559         manager = newDefaultPluginManager(new MultiplePluginLoader("test-atlassian-plugin-newer.xml"), new MultiplePluginLoader("test-atlassian-plugin.xml", "test-another-plugin.xml"));
560         manager.init();
561         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(2));
562     }
563 
564     @Test
565     public void testLoadOlderDuplicatePluginDoesNotTryToEnableIt()
566     {
567         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
568         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
569 
570         final Plugin plugin = new StaticPlugin()
571         {
572             @Override
573             protected PluginState enableInternal()
574             {
575                 fail("enable() must never be called on a earlier version of plugin when later version is installed");
576                 return null;
577             }
578 
579             @Override
580             public void disableInternal()
581             {
582                 fail("disable() must never be called on a earlier version of plugin when later version is installed");
583             }
584         };
585         plugin.setKey("test.atlassian.plugin");
586         plugin.getPluginInformation().setVersion("1.0");
587 
588         PluginLoader pluginLoader = new MultiplePluginLoader("test-atlassian-plugin-newer.xml");
589         manager = newDefaultPluginManager(pluginLoader);
590         manager.init();
591         manager.addPlugins(pluginLoader, Collections.singletonList(plugin));
592     }
593 
594     @Test
595     public void testLoadNewerDuplicatePlugin()
596     {
597         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
598         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
599 
600         manager = newDefaultPluginManager(
601                 new SinglePluginLoader("test-atlassian-plugin.xml"),
602                 new SinglePluginLoader("test-atlassian-plugin-newer.xml"));
603         manager.init();
604         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
605         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
606         assertEquals("1.1", plugin.getPluginInformation().getVersion());
607     }
608 
609     @Test
610     public void testLoadNewerDuplicateDynamicPluginPreservesPluginState() throws PluginParseException
611     {
612         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
613         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
614 
615         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin.xml"));
616         manager.init();
617 
618         pluginStateStore.save(PluginPersistentState.Builder.create(pluginStateStore.load()).setEnabled(manager.getPlugin("test.atlassian.plugin"),
619                 false).toState());
620 
621         assertFalse(getPluginAccessor().isPluginEnabled("test.atlassian.plugin"));
622         manager.shutdown();
623 
624         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin-newer.xml"));
625         manager.init();
626 
627         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
628         assertEquals("1.1", plugin.getPluginInformation().getVersion());
629         assertFalse(getPluginAccessor().isPluginEnabled("test.atlassian.plugin"));
630     }
631 
632     @Test
633     public void testLoadNewerDuplicateDynamicPluginPreservesModuleState() throws PluginParseException
634     {
635         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
636         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
637 
638         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin.xml"));
639         manager.init();
640 
641         pluginStateStore.save(PluginPersistentState.Builder.create(pluginStateStore.load()).setEnabled(
642                 getPluginAccessor().getPluginModule("test.atlassian.plugin:bear"), false).toState());
643 
644         manager.shutdown();
645 
646         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin-newer.xml"));
647         manager.init();
648 
649         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
650         assertEquals("1.1", plugin.getPluginInformation().getVersion());
651         assertFalse(getPluginAccessor().isPluginModuleEnabled("test.atlassian.plugin:bear"));
652         assertTrue(getPluginAccessor().isPluginModuleEnabled("test.atlassian.plugin:gold"));
653     }
654 
655     @Test
656     public void testLoadChangedDynamicPluginWithSameVersionNumberDoesNotReplaceExisting() throws PluginParseException
657     {
658         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
659         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
660 
661         manager = newDefaultPluginManager(
662                 new SinglePluginLoaderWithRemoval("test-atlassian-plugin.xml"),
663                 new SinglePluginLoaderWithRemoval("test-atlassian-plugin-changed-same-version.xml"));
664         manager.init();
665 
666         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
667         assertEquals("Test Plugin", plugin.getName());
668     }
669 
670     @Test
671     public void testGetPluginsWithPluginMatchingPluginPredicate() throws Exception
672     {
673         final Plugin plugin = mockTestPlugin(Collections.emptyList());
674 
675         final PluginPredicate mockPluginPredicate = mock(PluginPredicate.class);
676         when(mockPluginPredicate.matches(plugin)).thenReturn(true);
677 
678         manager = newDefaultPluginManager();
679         manager.addPlugins(null, Collections.singletonList(plugin));
680         final Collection<Plugin> plugins = getPluginAccessor().getPlugins(mockPluginPredicate);
681 
682         assertThat(plugins, hasSize(1));
683         assertTrue(plugins.contains(plugin));
684         verify(mockPluginPredicate).matches(any(Plugin.class));
685     }
686 
687     @Test
688     public void testGetPluginsWithPluginNotMatchingPluginPredicate() throws Exception
689     {
690         final Plugin plugin = mockTestPlugin(Collections.emptyList());
691 
692         final PluginPredicate mockPluginPredicate = mock(PluginPredicate.class);
693 
694         manager = newDefaultPluginManager();
695         manager.addPlugins(null, Collections.singletonList(plugin));
696         final Collection<Plugin> plugins = getPluginAccessor().getPlugins(mockPluginPredicate);
697 
698         assertThat(plugins, hasSize(0));
699     }
700 
701     @Test
702     public void testGetPluginModulesWithModuleMatchingPredicate() throws Exception
703     {
704         final MockThing module = new MockThing()
705         {
706         };
707         @SuppressWarnings("unchecked")
708         final ModuleDescriptor<MockThing> moduleDescriptor = mock(ModuleDescriptor.class);
709         when(moduleDescriptor.getModule()).thenReturn(module);
710         when(moduleDescriptor.getCompleteKey()).thenReturn("some-plugin-key:module");
711         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
712 
713         final Plugin plugin = mockTestPlugin(Collections.singleton(moduleDescriptor));
714         when(plugin.getModuleDescriptor("module")).thenReturn((ModuleDescriptor) moduleDescriptor);
715 
716         final ModuleDescriptorPredicate mockModulePredicate = mock(ModuleDescriptorPredicate.class);
717         when(mockModulePredicate.matches(moduleDescriptor)).thenReturn(true);
718 
719         manager = newDefaultPluginManager();
720         manager.addPlugins(null, Collections.singletonList(plugin));
721         @SuppressWarnings("unchecked")
722         final ModuleDescriptorPredicate<MockThing> predicate = (ModuleDescriptorPredicate<MockThing>) mockModulePredicate;
723         final Collection<MockThing> modules = getPluginAccessor().getModules(predicate);
724 
725         assertThat(modules, hasSize(1));
726         assertTrue(modules.contains(module));
727 
728         verify(mockModulePredicate).matches(moduleDescriptor);
729     }
730 
731     @Test
732     public void testGetPluginModulesWithGetModuleThrowingException() throws Exception
733     {
734         final Plugin badPlugin = new StaticPlugin();
735         badPlugin.setKey("bad");
736         final MockModuleDescriptor<Object> badDescriptor = new MockModuleDescriptor<Object>(badPlugin, "bad", new Object())
737         {
738             @Override
739             public Object getModule()
740             {
741                 throw new RuntimeException();
742             }
743         };
744         badPlugin.addModuleDescriptor(badDescriptor);
745 
746         final Plugin goodPlugin = new StaticPlugin();
747         goodPlugin.setKey("good");
748         final MockModuleDescriptor<Object> goodDescriptor = new MockModuleDescriptor<Object>(goodPlugin, "good", new Object());
749         goodPlugin.addModuleDescriptor(goodDescriptor);
750 
751         manager = newDefaultPluginManager();
752         manager.addPlugins(null, Arrays.asList(goodPlugin, badPlugin));
753         manager.enablePlugin("bad");
754         manager.enablePlugin("good");
755 
756         assertTrue(getPluginAccessor().isPluginEnabled("bad"));
757         assertTrue(getPluginAccessor().isPluginEnabled("good"));
758         final Collection<Object> modules = getPluginAccessor().getEnabledModulesByClass(Object.class);
759 
760         assertThat(modules, hasSize(1));
761         assertFalse(getPluginAccessor().isPluginEnabled("bad"));
762         assertTrue(getPluginAccessor().isPluginEnabled("good"));
763     }
764 
765     @Test
766     public void testGetPluginModulesWith2GetModulesThrowingExceptionOnlyNotifiesOnce() throws Exception
767     {
768         final Plugin badPlugin = new StaticPlugin();
769         badPlugin.setKey("bad");
770         final MockModuleDescriptor<Object> badDescriptor = new MockModuleDescriptor<Object>(badPlugin, "bad", new Object())
771         {
772             @Override
773             public Object getModule()
774             {
775                 throw new RuntimeException();
776             }
777         };
778         badPlugin.addModuleDescriptor(badDescriptor);
779         final MockModuleDescriptor<Object> badDescriptor2 = new MockModuleDescriptor<Object>(badPlugin, "bad2", new Object())
780         {
781             @Override
782             public Object getModule()
783             {
784                 throw new RuntimeException();
785             }
786         };
787         badPlugin.addModuleDescriptor(badDescriptor2);
788 
789         final Plugin goodPlugin = new StaticPlugin();
790         goodPlugin.setKey("good");
791         final MockModuleDescriptor<Object> goodDescriptor = new MockModuleDescriptor<Object>(goodPlugin, "good", new Object());
792         goodPlugin.addModuleDescriptor(goodDescriptor);
793         DisabledPluginCounter counter = new DisabledPluginCounter();
794         pluginEventManager.register(counter);
795 
796         manager = newDefaultPluginManager();
797         manager.addPlugins(null, Arrays.asList(goodPlugin, badPlugin));
798         manager.enablePlugin("bad");
799         manager.enablePlugin("good");
800 
801         assertTrue(getPluginAccessor().isPluginEnabled("bad"));
802         assertTrue(getPluginAccessor().isPluginEnabled("good"));
803         final Collection<Object> modules = getPluginAccessor().getEnabledModulesByClass(Object.class);
804 
805         assertThat(modules, hasSize(1));
806         assertFalse(getPluginAccessor().isPluginEnabled("bad"));
807         assertTrue(getPluginAccessor().isPluginEnabled("good"));
808         assertEquals(1, counter.disableCount);
809     }
810 
811     public static class DisabledPluginCounter
812     {
813         int disableCount = 0;
814         @PluginEventListener
815         public void consume(PluginDisabledEvent element)
816         {
817             disableCount++;
818         }
819     }
820 
821     @Test
822     public void testGetPluginModulesWithModuleNotMatchingPredicate() throws Exception
823     {
824         @SuppressWarnings("unchecked")
825         final ModuleDescriptor<MockThing> moduleDescriptor = mock(ModuleDescriptor.class);
826         when(moduleDescriptor.getCompleteKey()).thenReturn("some-plugin-key:module");
827         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
828 
829         final Plugin plugin = mockTestPlugin(Collections.singleton(moduleDescriptor));
830         when(plugin.getModuleDescriptor("module")).thenReturn((ModuleDescriptor) moduleDescriptor);
831 
832         @SuppressWarnings("unchecked")
833         final ModuleDescriptorPredicate<MockThing> predicate = mock(ModuleDescriptorPredicate.class);
834 
835         manager = newDefaultPluginManager();
836         manager.addPlugins(null, Collections.singletonList(plugin));
837         final Collection<MockThing> modules = getPluginAccessor().getModules(predicate);
838 
839         assertThat(modules, hasSize(0));
840 
841         verify(predicate).matches(moduleDescriptor);
842     }
843 
844     @Test
845     public void testGetPluginModuleDescriptorWithModuleMatchingPredicate() throws Exception
846     {
847         @SuppressWarnings("unchecked")
848         final ModuleDescriptor<MockThing> moduleDescriptor = mock(ModuleDescriptor.class);
849         when(moduleDescriptor.getCompleteKey()).thenReturn("some-plugin-key:module");
850         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
851 
852         final Plugin plugin = mockTestPlugin(Collections.singleton(moduleDescriptor));
853         when(plugin.getModuleDescriptor("module")).thenReturn((ModuleDescriptor) moduleDescriptor);
854 
855         @SuppressWarnings("unchecked")
856         final ModuleDescriptorPredicate<MockThing> predicate = mock(ModuleDescriptorPredicate.class);
857         when(predicate.matches(moduleDescriptor)).thenReturn(true);
858 
859         manager = newDefaultPluginManager();
860         manager.addPlugins(null, Collections.singletonList(plugin));
861         final Collection<ModuleDescriptor<MockThing>> modules = getPluginAccessor().getModuleDescriptors(predicate);
862 
863         assertThat(modules, hasSize(1));
864         assertTrue(modules.contains(moduleDescriptor));
865 
866         verify(predicate).matches(moduleDescriptor);
867     }
868 
869     @Test
870     public void testGetPluginModuleDescriptorsWithModuleNotMatchingPredicate() throws Exception
871     {
872         @SuppressWarnings("unchecked")
873         final ModuleDescriptor<MockThing> moduleDescriptor = mock(ModuleDescriptor.class);
874         when(moduleDescriptor.getCompleteKey()).thenReturn("some-plugin-key:module");
875         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
876 
877         final Plugin plugin = mockTestPlugin(Collections.singleton(moduleDescriptor));
878         when(plugin.getModuleDescriptor("module")).thenReturn((ModuleDescriptor) moduleDescriptor);
879 
880         @SuppressWarnings("unchecked")
881         final ModuleDescriptorPredicate<MockThing> predicate = mock(ModuleDescriptorPredicate.class);
882 
883         manager = newDefaultPluginManager();
884         manager.addPlugins(null, Collections.singletonList(plugin));
885         final Collection<MockThing> modules = getPluginAccessor().getModules(predicate);
886 
887         assertThat(modules, hasSize(0));
888 
889         verify(predicate).matches(moduleDescriptor);
890     }
891 
892     private Plugin mockTestPlugin(Collection moduleDescriptors)
893     {
894         final Plugin mockPlugin = mock(Plugin.class);
895         when(mockPlugin.getKey()).thenReturn("some-plugin-key");
896         when(mockPlugin.isEnabledByDefault()).thenReturn(true);
897         when(mockPlugin.isEnabled()).thenReturn(true);
898         when(mockPlugin.getPluginState()).thenReturn(PluginState.ENABLED);
899         when(mockPlugin.getModuleDescriptors()).thenReturn(moduleDescriptors);
900         return mockPlugin;
901     }
902 
903     @Test
904     public void testGetPluginAndModules() throws PluginParseException
905     {
906         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
907         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
908 
909         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
910         manager.init();
911 
912         final Plugin plugin = manager.getPlugin("test.atlassian.plugin");
913         assertNotNull(plugin);
914         assertEquals("Test Plugin", plugin.getName());
915 
916         final ModuleDescriptor<?> bear = plugin.getModuleDescriptor("bear");
917         assertEquals(bear, getPluginAccessor().getPluginModule("test.atlassian.plugin:bear"));
918     }
919 
920     @Test
921     public void testGetModuleByModuleClassOneFound() throws PluginParseException
922     {
923         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
924         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
925 
926         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
927         manager.init();
928 
929         final List<MockAnimalModuleDescriptor> animalDescriptors = getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class);
930         assertNotNull(animalDescriptors);
931         assertThat(animalDescriptors, hasSize(1));
932         final ModuleDescriptor<MockAnimal> moduleDescriptor = animalDescriptors.iterator().next();
933         assertEquals("Bear Animal", moduleDescriptor.getName());
934 
935         final List<MockMineralModuleDescriptor> mineralDescriptors = getPluginAccessor().getEnabledModuleDescriptorsByClass(MockMineralModuleDescriptor.class);
936         assertNotNull(mineralDescriptors);
937         assertThat(mineralDescriptors, hasSize(1));
938         final ModuleDescriptor<MockMineral> mineralDescriptor = mineralDescriptors.iterator().next();
939         assertEquals("Bar", mineralDescriptor.getName());
940     }
941 
942     @Test
943     public void testGetModuleByModuleClassAndDescriptor() throws PluginParseException
944     {
945         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
946         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
947 
948         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
949         manager.init();
950 
951         final Collection<MockBear> bearModules = getPluginAccessor().getEnabledModulesByClassAndDescriptor(
952                 new Class[]{MockAnimalModuleDescriptor.class, MockMineralModuleDescriptor.class}, MockBear.class);
953         assertNotNull(bearModules);
954         assertThat(bearModules, hasSize(1));
955         assertTrue(bearModules.iterator().next() instanceof MockBear);
956 
957         final Collection<MockBear> noModules = getPluginAccessor().getEnabledModulesByClassAndDescriptor(new Class[]{}, MockBear.class);
958         assertNotNull(noModules);
959         assertThat(noModules, hasSize(0));
960 
961         final Collection<MockThing> mockThings = getPluginAccessor().getEnabledModulesByClassAndDescriptor(
962                 new Class[]{MockAnimalModuleDescriptor.class, MockMineralModuleDescriptor.class}, MockThing.class);
963         assertNotNull(mockThings);
964         assertThat(mockThings, hasSize(2));
965         assertTrue(mockThings.iterator().next() instanceof MockThing);
966         assertTrue(mockThings.iterator().next() instanceof MockThing);
967 
968         final Collection<MockThing> mockThingsFromMineral = getPluginAccessor().getEnabledModulesByClassAndDescriptor(
969                 new Class[]{MockMineralModuleDescriptor.class}, MockThing.class);
970         assertNotNull(mockThingsFromMineral);
971         assertThat(mockThingsFromMineral, hasSize(1));
972         final Object o = mockThingsFromMineral.iterator().next();
973         assertTrue(o instanceof MockMineral);
974     }
975 
976     @Test
977     public void testGetModuleByModuleClassNoneFound() throws PluginParseException
978     {
979         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
980         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
981 
982         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
983         manager.init();
984 
985         class MockSilver implements MockMineral
986         {
987             public int getWeight()
988             {
989                 return 3;
990             }
991         }
992 
993         final Collection<MockSilver> descriptors = getPluginAccessor().getEnabledModulesByClass(MockSilver.class);
994         assertNotNull(descriptors);
995         assertTrue(descriptors.isEmpty());
996     }
997 
998     @Test
999     public void testGetModuleDescriptorsByType() throws PluginParseException
1000     {
1001         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
1002         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
1003 
1004         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
1005         manager.init();
1006 
1007         Collection<ModuleDescriptor<MockThing>> descriptors = getPluginAccessor().getEnabledModuleDescriptorsByType("animal");
1008         assertNotNull(descriptors);
1009         assertThat(descriptors, hasSize(1));
1010         ModuleDescriptor<MockThing> moduleDescriptor = descriptors.iterator().next();
1011         assertEquals("Bear Animal", moduleDescriptor.getName());
1012 
1013         descriptors = getPluginAccessor().getEnabledModuleDescriptorsByType("mineral");
1014         assertNotNull(descriptors);
1015         assertThat(descriptors, hasSize(1));
1016         moduleDescriptor = descriptors.iterator().next();
1017         assertEquals("Bar", moduleDescriptor.getName());
1018 
1019         try
1020         {
1021             getPluginAccessor().getEnabledModuleDescriptorsByType("foobar");
1022         }
1023         catch (final IllegalArgumentException e)
1024         {
1025             fail("Shouldn't have thrown exception.");
1026         }
1027     }
1028 
1029     @Test
1030     public void testRetrievingDynamicResources() throws PluginParseException, IOException
1031     {
1032         createFillAndCleanTempPluginDirectory();
1033 
1034         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1035 
1036         final InputStream is = manager.getPluginResourceAsStream("test.atlassian.plugin.classloaded", "atlassian-plugin.xml");
1037         assertNotNull(is);
1038         IOUtils.closeQuietly(is);
1039     }
1040 
1041     @Test
1042     public void testGetDynamicPluginClass() throws IOException, PluginParseException
1043     {
1044         createFillAndCleanTempPluginDirectory();
1045 
1046         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1047         try
1048         {
1049             manager.getDynamicPluginClass("com.atlassian.plugin.mock.MockPooh");
1050         }
1051         catch (final ClassNotFoundException e)
1052         {
1053             fail(e.getMessage());
1054         }
1055     }
1056 
1057     @Test
1058     public void testGetEnabledPluginsDoesNotReturnEnablingPlugins() throws Exception
1059     {
1060         final PluginLoader mockPluginLoader = mock(PluginLoader.class);
1061 
1062         final Plugin firstPlugin = new StaticPlugin();
1063         firstPlugin.setKey("first");
1064         firstPlugin.setEnabledByDefault(false);
1065         firstPlugin.setPluginInformation(new PluginInformation());
1066 
1067         manager = newDefaultPluginManager();
1068         manager.enablePluginState(firstPlugin, pluginStateStore);
1069 
1070         final Plugin secondPlugin = new StaticPlugin()
1071         {
1072             public PluginState enableInternal()
1073             {
1074                 try
1075                 {
1076                     // Assert here when the first plugin has been started but this plugin still has not been loaded
1077                     assertThat(manager.getPlugins(), hasSize(2));
1078                     assertThat("First plugin should not be enabled", manager.getEnabledPlugins(), hasSize(0));
1079                 }
1080                 catch (Exception e)
1081                 {
1082                     throw new RuntimeException(e);
1083                 }
1084                 return PluginState.ENABLED;
1085             }
1086 
1087             public void disableInternal()
1088             {
1089                 // do nothing
1090             }
1091         };
1092         secondPlugin.setKey("second");
1093         secondPlugin.setEnabledByDefault(false);
1094         secondPlugin.setPluginInformation(new PluginInformation());
1095         manager.enablePluginState(secondPlugin, pluginStateStore);
1096 
1097         when(mockPluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(firstPlugin, secondPlugin));
1098 
1099         manager = newDefaultPluginManager(mockPluginLoader);
1100         manager.init();
1101     }
1102 
1103     @Test
1104     public void testFindingNewPlugins() throws PluginParseException, IOException
1105     {
1106         createFillAndCleanTempPluginDirectory();
1107 
1108         //delete paddington for the timebeing
1109         final File paddington = new File(pluginsTestDir, PADDINGTON_JAR);
1110         paddington.delete();
1111 
1112         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1113 
1114         assertThat(manager.getPlugins(), hasSize(1));
1115         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1116 
1117         //restore paddington to test plugins dir
1118         FileUtils.copyDirectory(pluginsDirectory, pluginsTestDir);
1119 
1120         final int foundFirstScan = manager.scanForNewPlugins();
1121         assertThat(foundFirstScan, is(1));
1122         assertThat(manager.getPlugins(), hasSize(2));
1123         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1124         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded"));
1125 
1126         final int foundSecondScan = manager.scanForNewPlugins();
1127         assertThat(foundSecondScan, is(0));
1128         assertThat(manager.getPlugins(), hasSize(2));
1129         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1130         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded"));
1131     }
1132 
1133     @Test
1134     public void testFindingNewPluginsNotLoadingRestartRequiredDescriptors() throws PluginParseException, IOException
1135     {
1136         createFillAndCleanTempPluginDirectory();
1137 
1138         final DynamicSinglePluginLoader dynamicSinglePluginLoader = new DynamicSinglePluginLoader("test.atlassian.plugin", "test-requiresRestart-plugin.xml");
1139         manager = makeClassLoadingPluginManager(dynamicSinglePluginLoader);
1140 
1141         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1142 
1143         assertThat(manager.getPlugins(), hasSize(2));
1144         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1145 
1146         // enable the dynamic plugin loader
1147         dynamicSinglePluginLoader.canLoad.set(true);
1148 
1149         manager.scanForNewPlugins();
1150         assertThat(manager.getPlugins(), hasSize(3));
1151         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1152         assertNotNull(manager.getPlugin("test.atlassian.plugin"));
1153 
1154         final Plugin plugin = manager.getPlugin("test.atlassian.plugin");
1155         assertTrue(plugin instanceof UnloadablePlugin);
1156         assertTrue(((UnloadablePlugin)plugin).getErrorText().contains("foo"));
1157 
1158         assertEquals(PluginRestartState.INSTALL, manager.getPluginRestartState("test.atlassian.plugin"));
1159     }
1160 
1161     /**
1162      * Tests upgrade of plugin where the old version didn't have any restart required module descriptors, but the new one does
1163      */
1164     @Test
1165     public void testFindingUpgradePluginsNotLoadingRestartRequiredDescriptors() throws PluginParseException, IOException
1166     {
1167         createFillAndCleanTempPluginDirectory();
1168 
1169         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1170 
1171         final DynamicSinglePluginLoader dynamicSinglePluginLoader = new DynamicSinglePluginLoader("test.atlassian.plugin.classloaded2", "test-requiresRestartWithUpgrade-plugin.xml");
1172         manager = makeClassLoadingPluginManager(dynamicSinglePluginLoader);
1173 
1174         assertThat(manager.getPlugins(), hasSize(2));
1175         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1176         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1177 
1178 
1179         dynamicSinglePluginLoader.canLoad.set(true);
1180 
1181         manager.scanForNewPlugins();
1182         assertThat(manager.getPlugins(), hasSize(2));
1183         assertNotNull(manager.getPlugin("test.atlassian.plugin.classloaded2"));
1184         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.atlassian.plugin.classloaded2"));
1185         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1186     }
1187 
1188     @Test
1189     public void testInstallPluginThatRequiresRestart() throws PluginParseException, IOException, InterruptedException
1190     {
1191         createFillAndCleanTempPluginDirectory();
1192         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1193         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1194         assertThat(manager.getPlugins(), hasSize(2));
1195 
1196         new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1197                 "<atlassian-plugin name='Test 2' i18n-name-key='test.name' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1198                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1199         manager.scanForNewPlugins();
1200 
1201         assertThat(manager.getPlugins(), hasSize(3));
1202         Plugin plugin = manager.getPlugin("test.restartrequired");
1203         assertNotNull(plugin);
1204         assertEquals("Test 2", plugin.getName());
1205         assertEquals("test.name", plugin.getI18nNameKey());
1206         assertEquals(1, plugin.getPluginsVersion());
1207         assertEquals("1.0", plugin.getPluginInformation().getVersion());
1208         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1209         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1210         assertEquals(PluginRestartState.INSTALL, manager.getPluginRestartState("test.restartrequired"));
1211 
1212         manager.shutdown();
1213         manager.init();
1214 
1215         assertThat(manager.getPlugins(), hasSize(3));
1216         assertNotNull(plugin);
1217         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1218         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1219         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1220     }
1221 
1222     @Test
1223     public void testInstallPluginThatRequiresRestartThenRevert() throws PluginParseException, IOException, InterruptedException
1224     {
1225         createFillAndCleanTempPluginDirectory();
1226         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1227         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1228         manager.setPluginInstaller(new FilePluginInstaller(pluginsTestDir));
1229         assertThat(manager.getPlugins(), hasSize(2));
1230 
1231         File pluginJar = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1232                 "<atlassian-plugin name='Test 2' i18n-name-key='test.name' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1233                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build();
1234         manager.installPlugin(new JarPluginArtifact(pluginJar));
1235 
1236         assertThat(manager.getPlugins(), hasSize(3));
1237         Plugin plugin = manager.getPlugin("test.restartrequired");
1238         assertNotNull(plugin);
1239         assertEquals("Test 2", plugin.getName());
1240         assertEquals("test.name", plugin.getI18nNameKey());
1241         assertEquals(1, plugin.getPluginsVersion());
1242         assertEquals("1.0", plugin.getPluginInformation().getVersion());
1243         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1244         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1245         assertEquals(PluginRestartState.INSTALL, manager.getPluginRestartState("test.restartrequired"));
1246 
1247         manager.revertRestartRequiredChange("test.restartrequired");
1248         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1249 
1250         manager.shutdown();
1251         manager.init();
1252 
1253         assertThat(manager.getPlugins(), hasSize(2));
1254         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1255         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1256     }
1257 
1258     @Test
1259     public void testUpgradePluginThatRequiresRestart() throws PluginParseException, IOException, InterruptedException
1260     {
1261         createFillAndCleanTempPluginDirectory();
1262         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1263 
1264         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1265                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1266                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1267 
1268         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1269 
1270         assertThat(manager.getPlugins(), hasSize(3));
1271         assertNotNull(manager.getPlugin("test.restartrequired"));
1272         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1273         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1274         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1275 
1276         // Some filesystems only record last modified in seconds
1277         Thread.sleep(1000);
1278         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1279                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1280                 "    </plugin-info>", "    <requiresRestart key='foo' />", "    <requiresRestart key='bar' />", "</atlassian-plugin>").build();
1281 
1282         origFile.delete();
1283         FileUtils.moveFile(updateFile, origFile);
1284 
1285         manager.scanForNewPlugins();
1286         assertThat(manager.getPlugins(), hasSize(3));
1287         assertNotNull(manager.getPlugin("test.restartrequired"));
1288         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1289         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1290         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1291 
1292         manager.shutdown();
1293         manager.init();
1294 
1295         assertThat(manager.getPlugins(), hasSize(3));
1296         assertNotNull(manager.getPlugin("test.restartrequired"));
1297         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1298         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(2));
1299         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1300     }
1301 
1302     @Test
1303     public void testUpgradePluginThatRequiresRestartThenReverted() throws PluginParseException, IOException, InterruptedException
1304     {
1305         createFillAndCleanTempPluginDirectory();
1306         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1307 
1308         new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1309                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>",
1310                 "    <plugin-info>",
1311                 "        <version>1.0</version>",
1312                 "    </plugin-info>",
1313                 "    <requiresRestart key='foo' />",
1314                 "</atlassian-plugin>")
1315                 .build(pluginsTestDir);
1316 
1317         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1318         manager.setPluginInstaller(new FilePluginInstaller(pluginsTestDir));
1319 
1320         assertThat(manager.getPlugins(), hasSize(3));
1321         assertNotNull(manager.getPlugin("test.restartrequired"));
1322         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1323         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1324         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1325 
1326         // Some filesystems only record last modified in seconds
1327         Thread.sleep(1000);
1328         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1329                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1330                 "    </plugin-info>", "    <requiresRestart key='foo' />", "    <requiresRestart key='bar' />", "</atlassian-plugin>").build();
1331 
1332         manager.installPlugin(new JarPluginArtifact(updateFile));
1333 
1334         assertThat(manager.getPlugins(), hasSize(3));
1335         assertNotNull(manager.getPlugin("test.restartrequired"));
1336         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1337         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1338         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1339 
1340         manager.revertRestartRequiredChange("test.restartrequired");
1341         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1342 
1343         manager.shutdown();
1344         manager.init();
1345 
1346         assertThat(manager.getPlugins(), hasSize(3));
1347         assertNotNull(manager.getPlugin("test.restartrequired"));
1348         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1349         assertEquals("1.0", manager.getPlugin("test.restartrequired").getPluginInformation().getVersion());
1350         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1351         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1352     }
1353 
1354     @Test
1355     public void testUpgradePluginThatRequiresRestartThenRevertedRevertsToOriginalPlugin() throws PluginParseException, IOException, InterruptedException
1356     {
1357         createFillAndCleanTempPluginDirectory();
1358         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1359 
1360         // Add the plugin to the plugins test directory so it is included when we first start up
1361         new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1362                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1363                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1364 
1365         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1366         manager.setPluginInstaller(new FilePluginInstaller(pluginsTestDir));
1367 
1368         assertThat(manager.getPlugins(), hasSize(3));
1369         assertNotNull(manager.getPlugin("test.restartrequired"));
1370         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1371         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1372         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1373 
1374         // Some filesystems only record last modified in seconds
1375         Thread.sleep(1000);
1376         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1377                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1378                 "    </plugin-info>", "    <requiresRestart key='foo' />", "    <requiresRestart key='bar' />", "</atlassian-plugin>").build();
1379 
1380         // Install version 2 of the plugin
1381         manager.installPlugin(new JarPluginArtifact(updateFile));
1382 
1383         assertThat(manager.getPlugins(), hasSize(3));
1384         assertNotNull(manager.getPlugin("test.restartrequired"));
1385         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1386         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1387         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1388 
1389         Thread.sleep(1000);
1390         final File updateFile2 = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1391                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>3.0</version>",
1392                 "    </plugin-info>", "    <requiresRestart key='foo' />", "    <requiresRestart key='bar' />", "</atlassian-plugin>").build();
1393 
1394         // Install version 3 of the plugin
1395         manager.installPlugin(new JarPluginArtifact(updateFile2));
1396 
1397         assertThat(manager.getPlugins(), hasSize(3));
1398         assertNotNull(manager.getPlugin("test.restartrequired"));
1399         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1400         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1401         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1402 
1403         // Lets revert the whole upgrade so that the original plugin is restored
1404         manager.revertRestartRequiredChange("test.restartrequired");
1405         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1406         assertEquals("1.0", manager.getPlugin("test.restartrequired").getPluginInformation().getVersion());
1407 
1408         manager.shutdown();
1409         manager.init();
1410 
1411         assertThat(manager.getPlugins(), hasSize(3));
1412         assertNotNull(manager.getPlugin("test.restartrequired"));
1413         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1414         assertEquals("1.0", manager.getPlugin("test.restartrequired").getPluginInformation().getVersion());
1415         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1416         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1417     }
1418 
1419     @Test
1420     public void testUpgradePluginThatRequiresRestartMultipleTimeStaysUpgraded() throws PluginParseException, IOException, InterruptedException
1421     {
1422         createFillAndCleanTempPluginDirectory();
1423         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1424 
1425         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1426                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1427                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1428 
1429         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1430         manager.setPluginInstaller(new FilePluginInstaller(pluginsTestDir));
1431 
1432         assertThat(manager.getPlugins(), hasSize(3));
1433         assertNotNull(manager.getPlugin("test.restartrequired"));
1434         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1435         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1436         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1437 
1438         // Some filesystems only record last modified in seconds
1439         Thread.sleep(1000);
1440         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1441                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1442                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build();
1443 
1444         manager.installPlugin(new JarPluginArtifact(updateFile));
1445 
1446         assertThat(manager.getPlugins(), hasSize(3));
1447         assertNotNull(manager.getPlugin("test.restartrequired"));
1448         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1449         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1450         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1451 
1452         Thread.sleep(1000);
1453         final File updateFile2 = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1454                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>3.0</version>",
1455                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build();
1456 
1457         manager.installPlugin(new JarPluginArtifact(updateFile2));
1458 
1459         assertThat(manager.getPlugins(), hasSize(3));
1460         assertNotNull(manager.getPlugin("test.restartrequired"));
1461         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1462         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1463         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1464 
1465         manager.shutdown();
1466         manager.init();
1467 
1468         assertThat(manager.getPlugins(), hasSize(3));
1469         assertNotNull(manager.getPlugin("test.restartrequired"));
1470         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1471         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1472         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1473     }
1474 
1475     @Test
1476     public void testUpgradePluginThatPreviouslyRequiredRestart() throws PluginParseException, IOException, InterruptedException
1477     {
1478         createFillAndCleanTempPluginDirectory();
1479         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1480 
1481         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1482                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1483                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1484 
1485         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1486 
1487         assertThat(manager.getPlugins(), hasSize(3));
1488         assertNotNull(manager.getPlugin("test.restartrequired"));
1489         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1490         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1491         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1492 
1493         // Some filesystems only record last modified in seconds
1494         Thread.sleep(1000);
1495         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1496                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1497                 "    </plugin-info>", "</atlassian-plugin>").build(pluginsTestDir);
1498 
1499         origFile.delete();
1500         FileUtils.moveFile(updateFile, origFile);
1501 
1502         manager.scanForNewPlugins();
1503         assertThat(manager.getPlugins(), hasSize(3));
1504         assertNotNull(manager.getPlugin("test.restartrequired"));
1505         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1506         assertEquals(PluginRestartState.UPGRADE, manager.getPluginRestartState("test.restartrequired"));
1507         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1508 
1509         manager.shutdown();
1510         manager.init();
1511 
1512         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1513         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1514     }
1515 
1516     @Test
1517     public void testInstallPluginThatPreviouslyRequiredRestart() throws PluginParseException, IOException, InterruptedException
1518     {
1519         createFillAndCleanTempPluginDirectory();
1520         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1521 
1522         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1523 
1524         assertThat(manager.getPlugins(), hasSize(2));
1525         assertNull(manager.getPlugin("test.restartrequired"));
1526 
1527         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1528                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1529                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1530 
1531         manager.scanForNewPlugins();
1532         assertThat(manager.getPlugins(), hasSize(3));
1533         assertNotNull(manager.getPlugin("test.restartrequired"));
1534         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1535         assertEquals(PluginRestartState.INSTALL, manager.getPluginRestartState("test.restartrequired"));
1536 
1537         // Some filesystems only record last modified in seconds
1538         Thread.sleep(1000);
1539         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1540                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1541                 "    </plugin-info>", "</atlassian-plugin>").build(pluginsTestDir);
1542 
1543         origFile.delete();
1544         FileUtils.moveFile(updateFile, origFile);
1545 
1546         manager.scanForNewPlugins();
1547         assertThat(manager.getPlugins(), hasSize(3));
1548         assertNotNull(manager.getPlugin("test.restartrequired"));
1549         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1550         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1551         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1552 
1553         manager.shutdown();
1554         manager.init();
1555 
1556         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1557         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1558     }
1559 
1560     @Test
1561     public void testInstallPluginMoreThanOnceStaysAsInstall() throws PluginParseException, IOException, InterruptedException
1562     {
1563         createFillAndCleanTempPluginDirectory();
1564         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1565 
1566         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1567 
1568         assertThat(manager.getPlugins(), hasSize(2));
1569         assertNull(manager.getPlugin("test.restartrequired"));
1570         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1571 
1572         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1573                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1574                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1575 
1576         manager.scanForNewPlugins();
1577         assertThat(manager.getPlugins(), hasSize(3));
1578         assertNotNull(manager.getPlugin("test.restartrequired"));
1579         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1580         assertEquals(PluginRestartState.INSTALL, manager.getPluginRestartState("test.restartrequired"));
1581 
1582         // Some filesystems only record last modified in seconds
1583         Thread.sleep(1000);
1584         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1585                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1586                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1587 
1588         origFile.delete();
1589         FileUtils.moveFile(updateFile, origFile);
1590 
1591         manager.scanForNewPlugins();
1592         assertThat(manager.getPlugins(), hasSize(3));
1593         assertNotNull(manager.getPlugin("test.restartrequired"));
1594         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1595         assertEquals(PluginRestartState.INSTALL, manager.getPluginRestartState("test.restartrequired"));
1596 
1597         manager.shutdown();
1598         manager.init();
1599 
1600         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1601         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1602     }
1603 
1604     @Test
1605     public void testRemovePluginThatRequiresRestart() throws PluginParseException, IOException
1606     {
1607         createFillAndCleanTempPluginDirectory();
1608         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1609 
1610         final File pluginFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1611                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1612                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1613 
1614         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1615 
1616         assertThat(manager.getPlugins(), hasSize(3));
1617         assertNotNull(manager.getPlugin("test.restartrequired"));
1618         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1619         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1620         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1621 
1622         manager.uninstall(manager.getPlugin("test.restartrequired"));
1623 
1624         assertThat(manager.getPlugins(), hasSize(3));
1625         assertNotNull(manager.getPlugin("test.restartrequired"));
1626         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1627         assertEquals(PluginRestartState.REMOVE, manager.getPluginRestartState("test.restartrequired"));
1628         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1629 
1630         manager.shutdown();
1631         manager.init();
1632 
1633         assertFalse(pluginFile.exists());
1634         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1635         assertThat(manager.getPlugins(), hasSize(2));
1636     }
1637 
1638     @Test
1639     public void testRemovePluginThatRequiresRestartThenReverted() throws PluginParseException, IOException
1640     {
1641         createFillAndCleanTempPluginDirectory();
1642         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1643 
1644         final File pluginFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1645                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1646                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1647 
1648         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1649 
1650         assertThat(manager.getPlugins(), hasSize(3));
1651         assertNotNull(manager.getPlugin("test.restartrequired"));
1652         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1653         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1654         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1655 
1656         manager.uninstall(manager.getPlugin("test.restartrequired"));
1657 
1658         assertThat(manager.getPlugins(), hasSize(3));
1659         assertNotNull(manager.getPlugin("test.restartrequired"));
1660         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1661         assertEquals(PluginRestartState.REMOVE, manager.getPluginRestartState("test.restartrequired"));
1662         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1663 
1664         manager.revertRestartRequiredChange("test.restartrequired");
1665         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1666 
1667         manager.shutdown();
1668         manager.init();
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 
1677     @Test
1678     public void testRemovePluginThatRequiresRestartViaSubclass() throws PluginParseException, IOException
1679     {
1680         createFillAndCleanTempPluginDirectory();
1681         moduleDescriptorFactory.addModuleDescriptor("requiresRestartSubclass", RequiresRestartSubclassModuleDescriptor.class);
1682 
1683         final File pluginFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1684                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1685                 "    </plugin-info>", "    <requiresRestartSubclass key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1686 
1687         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1688 
1689         assertThat(manager.getPlugins(), hasSize(3));
1690         assertNotNull(manager.getPlugin("test.restartrequired"));
1691         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1692         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartSubclassModuleDescriptor.class), hasSize(1));
1693         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1694 
1695         manager.uninstall(manager.getPlugin("test.restartrequired"));
1696 
1697         assertThat(manager.getPlugins(), hasSize(3));
1698         assertNotNull(manager.getPlugin("test.restartrequired"));
1699         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1700         assertEquals(PluginRestartState.REMOVE, manager.getPluginRestartState("test.restartrequired"));
1701         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartSubclassModuleDescriptor.class), hasSize(1));
1702 
1703         manager.shutdown();
1704         manager.init();
1705 
1706         assertFalse(pluginFile.exists());
1707         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartSubclassModuleDescriptor.class), hasSize(0));
1708         assertThat(manager.getPlugins(), hasSize(2));
1709     }
1710 
1711     @Test
1712     public void testDisableEnableOfPluginThatRequiresRestart() throws PluginParseException, IOException
1713     {
1714         createFillAndCleanTempPluginDirectory();
1715         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1716 
1717         new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1718                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1719                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1720 
1721         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1722 
1723         assertThat(manager.getPlugins(), hasSize(3));
1724         assertNotNull(manager.getPlugin("test.restartrequired"));
1725         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1726         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1727         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1728 
1729         manager.disablePlugin("test.restartrequired");
1730         assertFalse(manager.isPluginEnabled("test.restartrequired"));
1731         manager.enablePlugins("test.restartrequired");
1732 
1733         assertThat(manager.getPlugins(), hasSize(3));
1734         assertNotNull(manager.getPlugin("test.restartrequired"));
1735         assertTrue(manager.isPluginEnabled("test.restartrequired"));
1736         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1737         assertThat(manager.getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1738     }
1739 
1740     @Test
1741     public void testCannotRemovePluginFromStaticLoader() throws PluginParseException, IOException
1742     {
1743         createFillAndCleanTempPluginDirectory();
1744         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1745 
1746         directoryPluginLoader = new DirectoryPluginLoader(
1747                 pluginsTestDir,
1748                 ImmutableList.<PluginFactory>of(new LegacyDynamicPluginFactory(PluginAccessor.Descriptor.FILENAME), new XmlDynamicPluginFactory(new MockApplication().setKey("key"))),
1749                 pluginEventManager);
1750 
1751         manager = newDefaultPluginManager(directoryPluginLoader, new SinglePluginLoader("test-requiresRestart-plugin.xml"));
1752         manager.init();
1753 
1754         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1755         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin"));
1756         assertTrue(getPluginAccessor().isPluginEnabled("test.atlassian.plugin"));
1757         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1758         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.atlassian.plugin"));
1759 
1760         try
1761         {
1762             manager.uninstall(getPluginAccessor().getPlugin("test.atlassian.plugin"));
1763             fail();
1764         }
1765         catch (final PluginException ex)
1766         {
1767             // test passed
1768         }
1769 
1770         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1771         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin"));
1772         assertTrue(getPluginAccessor().isPluginEnabled("test.atlassian.plugin"));
1773         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.atlassian.plugin"));
1774         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1775     }
1776 
1777     private DefaultPluginManager makeClassLoadingPluginManager(PluginLoader... pluginLoaders) throws PluginParseException
1778     {
1779         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
1780 
1781         directoryPluginLoader = new DirectoryPluginLoader(pluginsTestDir, ImmutableList.<PluginFactory>of(
1782                 new LegacyDynamicPluginFactory(PluginAccessor.Descriptor.FILENAME),
1783                 new XmlDynamicPluginFactory(new MockApplication().setKey("key"))), pluginEventManager);
1784 
1785         final DefaultPluginManager manager = newDefaultPluginManager(Iterables.toArray(ImmutableList.<PluginLoader>builder().add(directoryPluginLoader).addAll(copyOf(pluginLoaders)).build(), PluginLoader.class));
1786 
1787         manager.init();
1788         return manager;
1789     }
1790 
1791     @Test
1792     public void testRemovingPlugins() throws PluginException, IOException
1793     {
1794         createFillAndCleanTempPluginDirectory();
1795 
1796         final DefaultPluginManager manager = makeClassLoadingPluginManager();
1797         assertThat(manager.getPlugins(), hasSize(2));
1798         final MockAnimalModuleDescriptor moduleDescriptor = (MockAnimalModuleDescriptor) manager.getPluginModule("test.atlassian.plugin.classloaded:paddington");
1799         assertFalse(moduleDescriptor.disabled);
1800         final PassListener disabledListener = new PassListener(PluginDisabledEvent.class);
1801         pluginEventManager.register(disabledListener);
1802         final Plugin plugin = manager.getPlugin("test.atlassian.plugin.classloaded");
1803         manager.uninstall(plugin);
1804         assertTrue("Module must have had disable() called before being removed", moduleDescriptor.disabled);
1805 
1806         // uninstalling a plugin should remove it's state completely from the state store - PLUG-13
1807         assertTrue(pluginStateStore.load().getPluginStateMap(plugin).isEmpty());
1808 
1809         assertThat(manager.getPlugins(), hasSize(1));
1810         // plugin is no longer available though the plugin manager
1811         assertNull(manager.getPlugin("test.atlassian.plugin.classloaded"));
1812         assertEquals(1, pluginsTestDir.listFiles().length);
1813         disabledListener.assertCalled();
1814     }
1815 
1816     @Test
1817     public void testPluginModuleAvailableAfterInstallation()
1818     {
1819         Plugin plugin = mock(Plugin.class);
1820         when(plugin.getKey()).thenReturn("dynPlugin");
1821         when(plugin.isEnabledByDefault()).thenReturn(true);
1822         when(plugin.isDeleteable()).thenReturn(true);
1823         when(plugin.isUninstallable()).thenReturn(true);
1824         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1825         when(plugin.compareTo(any(Plugin.class))).thenReturn(-1);
1826 
1827         PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
1828         when(pluginLoader.supportsRemoval()).thenReturn(true);
1829 
1830         manager = newDefaultPluginManager(pluginLoader);
1831         manager.init();
1832 
1833         PluginModuleEnabledListener listener = new PluginModuleEnabledListener();
1834         pluginEventManager.register(listener);
1835         Collection<ModuleDescriptor<?>> mods = new ArrayList<ModuleDescriptor<?>>();
1836         MockModuleDescriptor<String> moduleDescriptor = new MockModuleDescriptor<String>(plugin, "foo", "foo");
1837         mods.add(moduleDescriptor);
1838         when(plugin.getModuleDescriptors()).thenReturn(mods);
1839         when(plugin.getModuleDescriptor("foo")).thenReturn((ModuleDescriptor)moduleDescriptor);
1840         pluginEventManager.broadcast(new PluginModuleAvailableEvent(moduleDescriptor));
1841 
1842         assertTrue(getPluginAccessor().isPluginModuleEnabled("dynPlugin:foo"));
1843         assertTrue(listener.called);
1844     }
1845 
1846     @Test
1847     public void testPluginModuleAvailableAfterInstallationButConfiguredToBeDisabled()
1848     {
1849         Plugin plugin = mock(Plugin.class);
1850         when(plugin.getKey()).thenReturn("dynPlugin");
1851         when(plugin.isEnabledByDefault()).thenReturn(true);
1852         when(plugin.isDeleteable()).thenReturn(true);
1853         when(plugin.isUninstallable()).thenReturn(true);
1854         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1855         when(plugin.compareTo(any(Plugin.class))).thenReturn(-1);
1856 
1857         PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
1858         when(pluginLoader.supportsRemoval()).thenReturn(true);
1859 
1860         manager = newDefaultPluginManager(pluginLoader);
1861         manager.init();
1862 
1863         MockModuleDescriptor<String> moduleDescriptor = new MockModuleDescriptor<String>(plugin, "foo", "foo");
1864 
1865         manager.disablePluginModuleState(moduleDescriptor, manager.getStore());
1866 
1867         PluginModuleEnabledListener listener = new PluginModuleEnabledListener();
1868         pluginEventManager.register(listener);
1869         Collection<ModuleDescriptor<?>> mods = new ArrayList<ModuleDescriptor<?>>();
1870         mods.add(moduleDescriptor);
1871 
1872         when(plugin.getModuleDescriptors()).thenReturn(mods);
1873         when(plugin.getModuleDescriptor("foo")).thenReturn((ModuleDescriptor)moduleDescriptor);
1874         pluginEventManager.broadcast(new PluginModuleAvailableEvent(moduleDescriptor));
1875 
1876         assertFalse(getPluginAccessor().isPluginModuleEnabled("dynPlugin:foo"));
1877         assertFalse(listener.called);
1878     }
1879 
1880     @Test
1881     public void testPluginModuleUnavailableAfterInstallation()
1882     {
1883         Plugin plugin = mock(Plugin.class);
1884         when(plugin.getKey()).thenReturn("dynPlugin");
1885         when(plugin.isEnabledByDefault()).thenReturn(true);
1886         when(plugin.isDeleteable()).thenReturn(true);
1887         when(plugin.isUninstallable()).thenReturn(true);
1888         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1889         when(plugin.compareTo(any(Plugin.class))).thenReturn(-1);
1890 
1891         PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
1892         when(pluginLoader.supportsRemoval()).thenReturn(true);
1893 
1894         manager = newDefaultPluginManager(pluginLoader);
1895         manager.init();
1896 
1897         PluginModuleDisabledListener listener = new PluginModuleDisabledListener();
1898         pluginEventManager.register(listener);
1899         Collection<ModuleDescriptor<?>> mods = new ArrayList<ModuleDescriptor<?>>();
1900         MockModuleDescriptor<String> moduleDescriptor = new MockModuleDescriptor<String>(plugin, "foo", "foo");
1901         mods.add(moduleDescriptor);
1902         when(plugin.getModuleDescriptors()).thenReturn(mods);
1903         when(plugin.getModuleDescriptor("foo")).thenReturn((ModuleDescriptor)moduleDescriptor);
1904         pluginEventManager.broadcast(new PluginModuleAvailableEvent(moduleDescriptor));
1905         assertTrue(getPluginAccessor().isPluginModuleEnabled("dynPlugin:foo"));
1906         assertFalse(listener.called);
1907         pluginEventManager.broadcast(new PluginModuleUnavailableEvent(moduleDescriptor));
1908         assertTrue(listener.called);
1909     }
1910 
1911     @Test
1912     public void testPluginContainerUnavailable()
1913     {
1914         Plugin plugin = mock(Plugin.class);
1915         when(plugin.getKey()).thenReturn("dynPlugin");
1916         when(plugin.isEnabledByDefault()).thenReturn(true);
1917         when(plugin.isDeleteable()).thenReturn(true);
1918         when(plugin.isUninstallable()).thenReturn(true);
1919         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1920         when(plugin.compareTo(any(Plugin.class))).thenReturn(-1);
1921         Collection<ModuleDescriptor<?>> mods = new ArrayList<ModuleDescriptor<?>>();
1922         MockModuleDescriptor<String> moduleDescriptor = new MockModuleDescriptor<String>(plugin, "foo", "foo");
1923         mods.add(moduleDescriptor);
1924         when(plugin.getModuleDescriptors()).thenReturn(mods);
1925         when(plugin.getModuleDescriptor("foo")).thenReturn((ModuleDescriptor)moduleDescriptor);
1926 
1927         PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
1928         when(pluginLoader.supportsRemoval()).thenReturn(true);
1929 
1930         manager = newDefaultPluginManager(pluginLoader);
1931         manager.init();
1932 
1933         PluginDisabledListener listener = new PluginDisabledListener();
1934         PluginModuleDisabledListener moduleDisabledListener = new PluginModuleDisabledListener();
1935         pluginEventManager.register(listener);
1936         pluginEventManager.register(moduleDisabledListener);
1937         when(plugin.getPluginState()).thenReturn(PluginState.DISABLED);
1938         pluginEventManager.broadcast(new PluginContainerUnavailableEvent("dynPlugin"));
1939         //Fix in behaviour, if the plugin is already disabled and you try to disable it there should be no event called
1940         assertFalse(getPluginAccessor().isPluginEnabled("dynPlugin"));
1941         assertFalse(listener.called);
1942         assertFalse(moduleDisabledListener.called);
1943     }
1944 
1945     @Test
1946     public void testUninstallPluginWithDependencies() throws PluginException, IOException
1947     {
1948         Plugin child = mock(Plugin.class);
1949         when(child.getKey()).thenReturn("child");
1950         when(child.isEnabledByDefault()).thenReturn(true);
1951         when(child.getPluginState()).thenReturn(PluginState.ENABLED);
1952         when(child.getRequiredPlugins()).thenReturn(singleton("parent"));
1953         when(child.compareTo(any(Plugin.class))).thenReturn(-1);
1954         Plugin parent = mock(Plugin.class);
1955         when(parent.getKey()).thenReturn("parent");
1956         when(parent.isEnabledByDefault()).thenReturn(true);
1957         when(parent.isDeleteable()).thenReturn(true);
1958         when(parent.isUninstallable()).thenReturn(true);
1959         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
1960         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
1961 
1962         PluginLoader pluginLoader = mockPluginLoaderForPlugins(child, parent);
1963         when(pluginLoader.supportsRemoval()).thenReturn(true);
1964 
1965         manager = newDefaultPluginManager(pluginLoader);
1966         manager.init();
1967 
1968         manager.uninstall(parent);
1969         verify(parent).enable();
1970         verify(parent).disable();
1971         verify(pluginLoader).removePlugin(parent);
1972 
1973         verify(child).enable();
1974         verify(child).disable();
1975     }
1976 
1977     @Test
1978     public void testUninstallPluginWithMultiLevelDependencies() throws PluginException, IOException
1979     {
1980 
1981         Plugin child = mock(Plugin.class);
1982         when(child.getKey()).thenReturn("child");
1983         when(child.isEnabledByDefault()).thenReturn(true);
1984         when(child.getPluginState()).thenReturn(PluginState.ENABLED);
1985         when(child.getRequiredPlugins()).thenReturn(singleton("parent"));
1986         when(child.compareTo(any(Plugin.class))).thenReturn(-1);
1987 
1988         Plugin parent = mock(Plugin.class);
1989         when(parent.getKey()).thenReturn("parent");
1990         when(parent.isEnabledByDefault()).thenReturn(true);
1991         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
1992         when(parent.getRequiredPlugins()).thenReturn(singleton("grandparent"));
1993         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
1994 
1995         Plugin grandparent = mock(Plugin.class);
1996         when(grandparent.getKey()).thenReturn("grandparent");
1997         when(grandparent.isEnabledByDefault()).thenReturn(true);
1998         when(grandparent.isDeleteable()).thenReturn(true);
1999         when(grandparent.isUninstallable()).thenReturn(true);
2000         when(grandparent.getPluginState()).thenReturn(PluginState.ENABLED);
2001         when(grandparent.compareTo(any(Plugin.class))).thenReturn(-1);
2002 
2003         PluginLoader pluginLoader = mockPluginLoaderForPlugins(child, parent, grandparent);
2004         when(pluginLoader.supportsRemoval()).thenReturn(true);
2005 
2006         manager = newDefaultPluginManager(pluginLoader);
2007         manager.init();
2008 
2009         manager.uninstall(grandparent);
2010         verify(grandparent).enable();
2011         verify(grandparent).disable();
2012         verify(pluginLoader).removePlugin(grandparent);
2013 
2014         verify(parent).enable();
2015         verify(parent).disable();
2016         verify(child).enable();
2017         verify(child).disable();
2018     }
2019 
2020     @Test
2021     public void testCircularDependencyWouldNotCauseInfiniteLoop() throws PluginException, IOException
2022     {
2023 
2024         Plugin p1 = mock(Plugin.class);
2025         when(p1.getKey()).thenReturn("p1");
2026         when(p1.isEnabledByDefault()).thenReturn(true);
2027         when(p1.getPluginState()).thenReturn(PluginState.ENABLED);
2028         when(p1.getRequiredPlugins()).thenReturn(ImmutableSet.of("p2", "parent"));
2029         when(p1.compareTo(any(Plugin.class))).thenReturn(-1);
2030 
2031         // Create a circular dependency between p1 and p2. This should not happen, but test anyway.
2032         Plugin p2 = mock(Plugin.class);
2033         when(p2.getKey()).thenReturn("p2");
2034         when(p2.isEnabledByDefault()).thenReturn(true);
2035         when(p2.getPluginState()).thenReturn(PluginState.ENABLED);
2036         when(p2.getRequiredPlugins()).thenReturn(singleton("p1"));
2037         when(p2.compareTo(any(Plugin.class))).thenReturn(-1);
2038 
2039         Plugin parent = mock(Plugin.class);
2040         when(parent.getKey()).thenReturn("parent");
2041         when(parent.isEnabledByDefault()).thenReturn(true);
2042         when(parent.isDeleteable()).thenReturn(true);
2043         when(parent.isUninstallable()).thenReturn(true);
2044         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
2045         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
2046 
2047         PluginLoader pluginLoader = mockPluginLoaderForPlugins(p1, p2, parent);
2048         when(pluginLoader.supportsRemoval()).thenReturn(true);
2049 
2050         manager = newDefaultPluginManager(pluginLoader);
2051         manager.init();
2052 
2053         manager.uninstall(parent);
2054         verify(parent, times(1)).enable();
2055         verify(parent, times(1)).disable();
2056         verify(pluginLoader).removePlugin(parent);
2057 
2058         verify(p1, times(1)).enable();
2059         verify(p1, times(1)).disable();
2060         verify(p2, times(1)).enable();
2061         verify(p2, times(1)).disable();
2062     }
2063 
2064     @Test
2065     public void testThreeCycleDependencyWouldNotCauseInfiniteLoop() throws PluginException, IOException
2066     {
2067         Plugin p1 = mock(Plugin.class);
2068         when(p1.getKey()).thenReturn("p1");
2069         when(p1.isEnabledByDefault()).thenReturn(true);
2070         when(p1.getPluginState()).thenReturn(PluginState.ENABLED);
2071         when(p1.getRequiredPlugins()).thenReturn(ImmutableSet.of("p2", "parent"));
2072         when(p1.compareTo(any(Plugin.class))).thenReturn(-1);
2073 
2074         // Create a circular dependency between p1, p2 and p3. This should not happen, but test anyway.
2075         Plugin p2 = mock(Plugin.class);
2076         when(p2.getKey()).thenReturn("p2");
2077         when(p2.isEnabledByDefault()).thenReturn(true);
2078         when(p2.getPluginState()).thenReturn(PluginState.ENABLED);
2079         when(p2.getRequiredPlugins()).thenReturn(singleton("p3"));
2080         when(p2.compareTo(any(Plugin.class))).thenReturn(-1);
2081 
2082         Plugin p3 = mock(Plugin.class);
2083         when(p3.getKey()).thenReturn("p3");
2084         when(p3.isEnabledByDefault()).thenReturn(true);
2085         when(p3.getPluginState()).thenReturn(PluginState.ENABLED);
2086         when(p3.getRequiredPlugins()).thenReturn(singleton("p1"));
2087         when(p3.compareTo(any(Plugin.class))).thenReturn(-1);
2088 
2089         Plugin parent = mock(Plugin.class);
2090         when(parent.getKey()).thenReturn("parent");
2091         when(parent.isEnabledByDefault()).thenReturn(true);
2092         when(parent.isDeleteable()).thenReturn(true);
2093         when(parent.isUninstallable()).thenReturn(true);
2094         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
2095         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
2096 
2097         PluginLoader pluginLoader = mockPluginLoaderForPlugins(p1, p2, p3, parent);
2098         when(pluginLoader.supportsRemoval()).thenReturn(true);
2099 
2100         manager = newDefaultPluginManager(pluginLoader);
2101         manager.init();
2102 
2103         manager.uninstall(parent);
2104         verify(parent, times(1)).enable();
2105         verify(parent, times(1)).disable();
2106         verify(pluginLoader).removePlugin(parent);
2107 
2108         verify(p1, times(1)).enable();
2109         verify(p1, times(1)).disable();
2110         verify(p2, times(1)).enable();
2111         verify(p2, times(1)).disable();
2112         verify(p3, times(1)).enable();
2113         verify(p3, times(1)).disable();
2114     }
2115 
2116     @Test
2117     public void testNonRemovablePlugins() throws PluginParseException
2118     {
2119         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
2120         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
2121 
2122         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
2123         manager.init();
2124 
2125         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
2126         assertFalse(plugin.isUninstallable());
2127         assertNotNull(plugin.getResourceAsStream("test-atlassian-plugin.xml"));
2128 
2129         try
2130         {
2131             manager.uninstall(plugin);
2132             fail("Where was the exception?");
2133         }
2134         catch (final PluginException p)
2135         {
2136         }
2137     }
2138 
2139     @Test
2140     public void testNonDeletablePlugins() throws PluginException, IOException
2141     {
2142         createFillAndCleanTempPluginDirectory();
2143 
2144         final DefaultPluginManager manager = makeClassLoadingPluginManager();
2145         assertThat(manager.getPlugins(), hasSize(2));
2146 
2147         // Set plugin file can't be deleted.
2148         final Plugin pluginToRemove = new AbstractDelegatingPlugin(manager.getPlugin("test.atlassian.plugin.classloaded"))
2149         {
2150             public boolean isDeleteable()
2151             {
2152                 return false;
2153             }
2154         };
2155 
2156         // Disable plugin module before uninstall
2157         final MockAnimalModuleDescriptor moduleDescriptor = (MockAnimalModuleDescriptor) manager.getPluginModule("test.atlassian.plugin.classloaded:paddington");
2158         assertFalse(moduleDescriptor.disabled);
2159 
2160         manager.uninstall(pluginToRemove);
2161 
2162         assertTrue("Module must have had disable() called before being removed", moduleDescriptor.disabled);
2163         assertThat(manager.getPlugins(), hasSize(1));
2164         assertNull(manager.getPlugin("test.atlassian.plugin.classloaded"));
2165         assertEquals(2, pluginsTestDir.listFiles().length);
2166     }
2167 
2168     // These methods test the plugin compareTo() function, which compares plugins based on their version numbers.
2169     @Test
2170     public void testComparePluginNewer()
2171     {
2172 
2173         final Plugin p1 = createPluginWithVersion("1.1");
2174         final Plugin p2 = createPluginWithVersion("1.0");
2175         assertTrue(p1.compareTo(p2) == 1);
2176 
2177         p1.getPluginInformation().setVersion("1.10");
2178         p2.getPluginInformation().setVersion("1.2");
2179         assertTrue(p1.compareTo(p2) == 1);
2180 
2181         p1.getPluginInformation().setVersion("1.2");
2182         p2.getPluginInformation().setVersion("1.01");
2183         assertTrue(p1.compareTo(p2) == 1);
2184 
2185         p1.getPluginInformation().setVersion("1.0.1");
2186         p2.getPluginInformation().setVersion("1.0");
2187         assertTrue(p1.compareTo(p2) == 1);
2188 
2189         p1.getPluginInformation().setVersion("1.2");
2190         p2.getPluginInformation().setVersion("1.1.1");
2191         assertTrue(p1.compareTo(p2) == 1);
2192     }
2193 
2194     @Test
2195     public void testComparePluginOlder()
2196     {
2197         final Plugin p1 = createPluginWithVersion("1.0");
2198         final Plugin p2 = createPluginWithVersion("1.1");
2199         assertTrue(p1.compareTo(p2) == -1);
2200 
2201         p1.getPluginInformation().setVersion("1.2");
2202         p2.getPluginInformation().setVersion("1.10");
2203         assertTrue(p1.compareTo(p2) == -1);
2204 
2205         p1.getPluginInformation().setVersion("1.01");
2206         p2.getPluginInformation().setVersion("1.2");
2207         assertTrue(p1.compareTo(p2) == -1);
2208 
2209         p1.getPluginInformation().setVersion("1.0");
2210         p2.getPluginInformation().setVersion("1.0.1");
2211         assertTrue(p1.compareTo(p2) == -1);
2212 
2213         p1.getPluginInformation().setVersion("1.1.1");
2214         p2.getPluginInformation().setVersion("1.2");
2215         assertTrue(p1.compareTo(p2) == -1);
2216     }
2217 
2218     @Test
2219     public void testComparePluginEqual()
2220     {
2221         final Plugin p1 = createPluginWithVersion("1.0");
2222         final Plugin p2 = createPluginWithVersion("1.0");
2223         assertTrue(p1.compareTo(p2) == 0);
2224 
2225         p1.getPluginInformation().setVersion("1.1.0.0");
2226         p2.getPluginInformation().setVersion("1.1");
2227         assertTrue(p1.compareTo(p2) == 0);
2228 
2229         p1.getPluginInformation().setVersion(" 1 . 1 ");
2230         p2.getPluginInformation().setVersion("1.1");
2231         assertTrue(p1.compareTo(p2) == 0);
2232     }
2233 
2234     // If we can't understand the version of a plugin, then take the new one.
2235     @Test
2236     public void testComparePluginNoVersion()
2237     {
2238         final Plugin p1 = createPluginWithVersion("1.0");
2239         final Plugin p2 = createPluginWithVersion("#$%");
2240         assertEquals(1, p1.compareTo(p2));
2241 
2242         p1.getPluginInformation().setVersion("#$%");
2243         p2.getPluginInformation().setVersion("1.0");
2244         assertEquals(-1, p1.compareTo(p2));
2245     }
2246 
2247     @Test
2248     public void testComparePluginBadPlugin()
2249     {
2250         final Plugin p1 = createPluginWithVersion("1.0");
2251         final Plugin p2 = createPluginWithVersion("1.0");
2252 
2253         // Compare against something with a different key
2254         p2.setKey("bad.key");
2255         assertTrue(p1.compareTo(p2) != 0);
2256     }
2257 
2258     @Test
2259     public void testInvalidationOfDynamicResourceCache() throws IOException, PluginException
2260     {
2261         createFillAndCleanTempPluginDirectory();
2262 
2263         final DefaultPluginManager manager = makeClassLoadingPluginManager();
2264 
2265         checkResources(manager, true, true);
2266         manager.disablePlugin("test.atlassian.plugin.classloaded");
2267         checkResources(manager, false, false);
2268         manager.enablePlugin("test.atlassian.plugin.classloaded");
2269         checkResources(manager, true, true);
2270         manager.uninstall(manager.getPlugin("test.atlassian.plugin.classloaded"));
2271         checkResources(manager, false, false);
2272         //restore paddington to test plugins dir
2273         FileUtils.copyDirectory(pluginsDirectory, pluginsTestDir);
2274         manager.scanForNewPlugins();
2275         checkResources(manager, true, true);
2276         // Resources from disabled modules are still available
2277         //manager.disablePluginModule("test.atlassian.plugin.classloaded:paddington");
2278         //checkResources(manager, true, false);
2279     }
2280 
2281     @Test
2282     public void testValidatePlugin() throws PluginParseException
2283     {
2284         final DynamicPluginLoader mockLoader = mock(DynamicPluginLoader.class);
2285         when(mockLoader.isDynamicPluginLoader()).thenReturn(true);
2286 
2287         manager = new DefaultPluginManager(pluginStateStore, ImmutableList.<PluginLoader>of(mockLoader), moduleDescriptorFactory, new DefaultPluginEventManager());
2288 
2289         final PluginArtifact mockPluginJar = mock(PluginArtifact.class);
2290         final PluginArtifact pluginArtifact = mockPluginJar;
2291         when(mockLoader.canLoad(pluginArtifact)).thenReturn("foo");
2292 
2293         final String key = manager.validatePlugin(pluginArtifact);
2294         assertEquals("foo", key);
2295         verify(mockLoader).canLoad(pluginArtifact);
2296     }
2297 
2298     @Test
2299     public void testValidatePluginWithNoDynamicLoaders() throws PluginParseException
2300     {
2301         final PluginLoader loader = mock(PluginLoader.class);
2302         final DefaultPluginManager manager = new DefaultPluginManager(pluginStateStore, ImmutableList.<PluginLoader>of(loader), moduleDescriptorFactory, new DefaultPluginEventManager());
2303 
2304         final PluginArtifact pluginArtifact = mock(PluginArtifact.class);
2305         try
2306         {
2307             manager.validatePlugin(pluginArtifact);
2308             fail("Should have thrown exception");
2309         }
2310         catch (final IllegalStateException ex)
2311         {
2312             // test passed
2313         }
2314     }
2315 
2316     @Test
2317     public void testInvalidationOfDynamicClassCache() throws IOException, PluginException
2318     {
2319         createFillAndCleanTempPluginDirectory();
2320 
2321         final DefaultPluginManager manager = makeClassLoadingPluginManager();
2322 
2323         checkClasses(manager, true);
2324         manager.disablePlugin("test.atlassian.plugin.classloaded");
2325         checkClasses(manager, false);
2326         manager.enablePlugin("test.atlassian.plugin.classloaded");
2327         checkClasses(manager, true);
2328         manager.uninstall(manager.getPlugin("test.atlassian.plugin.classloaded"));
2329         checkClasses(manager, false);
2330         //restore paddington to test plugins dir
2331         FileUtils.copyDirectory(pluginsDirectory, pluginsTestDir);
2332         manager.scanForNewPlugins();
2333         checkClasses(manager, true);
2334     }
2335 
2336     @Test
2337     public void testInstallPlugin() throws Exception
2338     {
2339         final PluginPersistentStateStore mockPluginStateStore = mock(PluginPersistentStateStore.class);
2340         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
2341         final DynamicPluginLoader mockPluginLoader = mock(DynamicPluginLoader.class);
2342         when(mockPluginLoader.isDynamicPluginLoader()).thenReturn(true);
2343 
2344         final DescriptorParserFactory mockDescriptorParserFactory = mock(DescriptorParserFactory.class);
2345         final DescriptorParser mockDescriptorParser = mock(DescriptorParser.class);
2346         final PluginArtifact pluginArtifact = mock(PluginArtifact.class);
2347         final PluginInstaller mockRepository = mock(PluginInstaller.class);
2348         final Plugin plugin = mock(Plugin.class);
2349 
2350 
2351         final DefaultPluginManager pluginManager = new DefaultPluginManager(mockPluginStateStore,
2352                 Collections.<PluginLoader>singletonList(mockPluginLoader), moduleDescriptorFactory, pluginEventManager);
2353 
2354         when(mockPluginStateStore.load()).thenReturn(new DefaultPluginPersistentState());
2355         when(mockPluginStateStore.load()).thenReturn(new DefaultPluginPersistentState());
2356         when(mockPluginStateStore.load()).thenReturn(new DefaultPluginPersistentState());
2357         when(mockDescriptorParser.getKey()).thenReturn("test");
2358         when(mockPluginLoader.loadAllPlugins(moduleDescriptorFactory)).thenReturn((Iterable) Collections.emptyList());
2359         when(mockPluginLoader.supportsAddition()).thenReturn(true);
2360         when(mockPluginLoader.loadFoundPlugins(moduleDescriptorFactory)).thenReturn(Collections.singletonList(plugin));
2361         when(mockPluginLoader.canLoad(pluginArtifact)).thenReturn("test");
2362         when(plugin.getKey()).thenReturn("test");
2363         when(plugin.getModuleDescriptors()).thenReturn((Collection) new ArrayList<Object>());
2364         when(plugin.getModuleDescriptors()).thenReturn((Collection) new ArrayList<Object>());
2365         when(plugin.isEnabledByDefault()).thenReturn(true);
2366 
2367         when(plugin.isEnabledByDefault()).thenReturn(true);
2368         when(plugin.isEnabled()).thenReturn(true);
2369         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
2370         when(plugin.hasAllPermissions()).thenReturn(true);
2371         when(plugin.getActivePermissions()).thenReturn(ImmutableSet.of(Permissions.ALL_PERMISSIONS));
2372 
2373         pluginManager.setPluginInstaller(mockRepository);
2374         pluginManager.init();
2375         final PassListener enabledListener = new PassListener(PluginEnabledEvent.class);
2376         pluginEventManager.register(enabledListener);
2377         pluginManager.installPlugin(pluginArtifact);
2378 
2379         assertEquals(plugin, pluginManager.getPlugin("test"));
2380         assertTrue(pluginManager.isPluginEnabled("test"));
2381 
2382 //        plugin.verify();
2383 //        mockRepository.verify();
2384 //        pluginArtifact.verify();
2385 //        mockDescriptorParser.verify();
2386 //        mockDescriptorParserFactory.verify();
2387 //        mockPluginLoader.verify();
2388 //        mockPluginStateStore.verify();
2389         enabledListener.assertCalled();
2390     }
2391 
2392     @Test
2393     public void testInstallPluginsWithOne()
2394     {
2395         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
2396         when(loader.isDynamicPluginLoader()).thenReturn(true);
2397 
2398         ModuleDescriptorFactory descriptorFactory = mock(ModuleDescriptorFactory.class);
2399         PluginEventManager eventManager = mock(PluginEventManager.class);
2400         PluginInstaller installer = mock(PluginInstaller.class);
2401         DefaultPluginManager pm = new DefaultPluginManager(new MemoryPluginPersistentStateStore(), Collections.<PluginLoader>singletonList(loader), descriptorFactory, eventManager);
2402         pm.setPluginInstaller(installer);
2403         when(loader.loadAllPlugins(descriptorFactory)).thenReturn(Arrays.<Plugin>asList());
2404         PluginArtifact artifact = mock(PluginArtifact.class);
2405         Plugin plugin = mock(Plugin.class);
2406         when(loader.canLoad(artifact)).thenReturn("foo");
2407         when(loader.loadFoundPlugins(descriptorFactory)).thenReturn(Arrays.asList(plugin));
2408 
2409         pm.init();
2410         assertThat(pm.getPlugins(), empty());
2411         pm.installPlugins(artifact);
2412 
2413         verify(loader).canLoad(artifact);
2414         verify(installer).installPlugin("foo", artifact);
2415     }
2416 
2417     @Test
2418     public void testInstallPluginsWithTwo()
2419     {
2420         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
2421         when(loader.isDynamicPluginLoader()).thenReturn(true);
2422 
2423         ModuleDescriptorFactory descriptorFactory = mock(ModuleDescriptorFactory.class);
2424         PluginEventManager eventManager = mock(PluginEventManager.class);
2425         PluginInstaller installer = mock(PluginInstaller.class);
2426         DefaultPluginManager pm = new DefaultPluginManager(new MemoryPluginPersistentStateStore(), Collections.<PluginLoader>singletonList(loader), descriptorFactory, eventManager);
2427         pm.setPluginInstaller(installer);
2428         when(loader.loadAllPlugins(descriptorFactory)).thenReturn(Arrays.<Plugin>asList());
2429         PluginArtifact artifactA = mock(PluginArtifact.class);
2430         Plugin pluginA = mock(Plugin.class);
2431         when(loader.canLoad(artifactA)).thenReturn("a");
2432         PluginArtifact artifactB = mock(PluginArtifact.class);
2433         Plugin pluginB = mock(Plugin.class);
2434         when(loader.canLoad(artifactB)).thenReturn("b");
2435 
2436         when(loader.loadFoundPlugins(descriptorFactory)).thenReturn(Arrays.asList(pluginA, pluginB));
2437 
2438         pm.init();
2439         assertThat(pm.getPlugins(), empty());
2440         pm.installPlugins(artifactA, artifactB);
2441 
2442         verify(loader).canLoad(artifactA);
2443         verify(loader).canLoad(artifactB);
2444         verify(installer).installPlugin("a", artifactA);
2445         verify(installer).installPlugin("b", artifactB);
2446     }
2447 
2448     @Test
2449     public void testInstallPluginsWithTwoButOneFailsValidation()
2450     {
2451         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
2452         when(loader.isDynamicPluginLoader()).thenReturn(true);
2453 
2454         ModuleDescriptorFactory descriptorFactory = mock(ModuleDescriptorFactory.class);
2455         PluginEventManager eventManager = mock(PluginEventManager.class);
2456         PluginInstaller installer = mock(PluginInstaller.class);
2457         DefaultPluginManager pm = new DefaultPluginManager(new MemoryPluginPersistentStateStore(), Collections.<PluginLoader>singletonList(loader), descriptorFactory, eventManager);
2458         pm.setPluginInstaller(installer);
2459         PluginArtifact artifactA = mock(PluginArtifact.class);
2460         Plugin pluginA = mock(Plugin.class);
2461         when(loader.canLoad(artifactA)).thenReturn("a");
2462         PluginArtifact artifactB = mock(PluginArtifact.class);
2463         Plugin pluginB = mock(Plugin.class);
2464         when(loader.canLoad(artifactB)).thenReturn(null);
2465 
2466         when(loader.loadFoundPlugins(descriptorFactory)).thenReturn(Arrays.asList(pluginA, pluginB));
2467 
2468         try
2469         {
2470             pm.installPlugins(artifactA, artifactB);
2471             fail("Should have not installed plugins");
2472         }
2473         catch (PluginParseException ex)
2474         {
2475             // this is good
2476         }
2477 
2478         verify(loader).canLoad(artifactA);
2479         verify(loader).canLoad(artifactB);
2480         verify(installer, never()).installPlugin("a", artifactA);
2481         verify(installer, never()).installPlugin("b", artifactB);
2482     }
2483 
2484     @Test
2485     public void testInstallPluginsWithTwoButOneFailsValidationWithException()
2486     {
2487         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
2488         when(loader.isDynamicPluginLoader()).thenReturn(true);
2489 
2490         ModuleDescriptorFactory descriptorFactory = mock(ModuleDescriptorFactory.class);
2491         PluginEventManager eventManager = mock(PluginEventManager.class);
2492         PluginInstaller installer = mock(PluginInstaller.class);
2493         DefaultPluginManager pm = new DefaultPluginManager(new MemoryPluginPersistentStateStore(), Collections.<PluginLoader>singletonList(loader), descriptorFactory, eventManager);
2494         pm.setPluginInstaller(installer);
2495         PluginArtifact artifactA = mock(PluginArtifact.class);
2496         Plugin pluginA = mock(Plugin.class);
2497         when(loader.canLoad(artifactA)).thenReturn("a");
2498         PluginArtifact artifactB = mock(PluginArtifact.class);
2499         Plugin pluginB = mock(Plugin.class);
2500         doThrow(new PluginParseException()).when(loader).canLoad(artifactB);
2501 
2502         when(loader.loadFoundPlugins(descriptorFactory)).thenReturn(Arrays.asList(pluginA, pluginB));
2503 
2504         try
2505         {
2506             pm.installPlugins(artifactA, artifactB);
2507             fail("Should have not installed plugins");
2508         }
2509         catch (PluginParseException ex)
2510         {
2511             // this is good
2512         }
2513 
2514         verify(loader).canLoad(artifactA);
2515         verify(loader).canLoad(artifactB);
2516         verify(installer, never()).installPlugin("a", artifactA);
2517         verify(installer, never()).installPlugin("b", artifactB);
2518     }
2519 
2520     Plugin mockStaticPlugin(final String pluginKey, final ModuleDescriptor<?>... descriptors)
2521     {
2522         return new StaticPlugin()
2523         {
2524             {
2525                 setPluginInformation(new PluginInformation());
2526                 setEnabledByDefault(true);
2527                 setKey(pluginKey);
2528             }
2529 
2530             @Override
2531             public Collection<ModuleDescriptor<?>> getModuleDescriptors()
2532             {
2533                 return Arrays.<ModuleDescriptor<?>>asList(descriptors);
2534             }
2535 
2536             @Override
2537             public ModuleDescriptor<Object> getModuleDescriptor(final String moduleKey)
2538             {
2539                 for (ModuleDescriptor desc : descriptors)
2540                 {
2541                     if (desc.getKey().equals(moduleKey))
2542                         return desc;
2543                 }
2544                 return null;
2545             }
2546         };
2547     }
2548     
2549     @Test
2550     public void testInstallTwoPluginsButOneFailsToEnableAModuleAndThenFailsToDisableAModule()
2551     {
2552         final PluginLoader mockPluginLoader = mock(PluginLoader.class);
2553         final ModuleDescriptor<Object> failEnableModuleDescriptor = mockFailingModuleDescriptor("foo:bar", FailureMode.FAIL_TO_ENABLE);
2554         final ModuleDescriptor<Object> failDisableModuleDescriptor = mockFailingModuleDescriptor("foo:buzz", FailureMode.FAIL_TO_DISABLE);
2555 
2556         Plugin badPlugin = mockStaticPlugin("foo", failDisableModuleDescriptor, failEnableModuleDescriptor);
2557 
2558         final AbstractModuleDescriptor<?> goodModuleDescriptor = mock(AbstractModuleDescriptor.class);
2559         when(goodModuleDescriptor.getKey()).thenReturn("baz");
2560         when(goodModuleDescriptor.getCompleteKey()).thenReturn("good:baz");
2561         when(goodModuleDescriptor.isEnabledByDefault()).thenReturn(true);
2562         Plugin goodPlugin = mockStaticPlugin("good", goodModuleDescriptor);
2563 
2564         when(mockPluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Lists.newArrayList(badPlugin, goodPlugin));
2565 
2566         manager = newDefaultPluginManager(mockPluginLoader);
2567         manager.init();
2568 
2569         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
2570         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
2571         verify(goodModuleDescriptor).enabled();
2572     }
2573 
2574     private <T> void checkResources(final PluginAccessor manager, final boolean canGetGlobal, final boolean canGetModule) throws IOException
2575     {
2576         InputStream is = manager.getDynamicResourceAsStream("icon.gif");
2577         assertEquals(canGetGlobal, is != null);
2578         IOUtils.closeQuietly(is);
2579         is = manager.getDynamicResourceAsStream("bear/paddington.vm");
2580         assertEquals(canGetModule, is != null);
2581         IOUtils.closeQuietly(is);
2582     }
2583 
2584     private <T> void checkClasses(final PluginAccessor manager, final boolean canGet)
2585     {
2586         try
2587         {
2588             manager.getDynamicPluginClass("com.atlassian.plugin.mock.MockPaddington");
2589             if (!canGet)
2590             {
2591                 fail("Class in plugin was successfully loaded");
2592             }
2593         }
2594         catch (final ClassNotFoundException e)
2595         {
2596             if (canGet)
2597             {
2598                 fail(e.getMessage());
2599             }
2600         }
2601     }
2602 
2603     @Test
2604     public void testAddPluginsThatThrowExceptionOnEnabled() throws Exception
2605     {
2606         final Plugin plugin = new CannotEnablePlugin();
2607 
2608         manager = newDefaultPluginManager();
2609         manager.addPlugins(null, Arrays.asList(plugin));
2610 
2611         assertFalse(plugin.getPluginState() == PluginState.ENABLED);
2612     }
2613 
2614     @Test
2615     public void testUninstallPluginClearsState() throws IOException
2616     {
2617         createFillAndCleanTempPluginDirectory();
2618 
2619         final DefaultPluginManager manager = makeClassLoadingPluginManager();
2620 
2621         checkClasses(manager, true);
2622         final Plugin plugin = manager.getPlugin("test.atlassian.plugin.classloaded");
2623 
2624         final ModuleDescriptor<?> module = plugin.getModuleDescriptor("paddington");
2625         assertTrue(manager.isPluginModuleEnabled(module.getCompleteKey()));
2626         manager.disablePluginModule(module.getCompleteKey());
2627         assertFalse(manager.isPluginModuleEnabled(module.getCompleteKey()));
2628         manager.uninstall(plugin);
2629         assertFalse(manager.isPluginModuleEnabled(module.getCompleteKey()));
2630         assertTrue(pluginStateStore.load().getPluginStateMap(plugin).isEmpty());
2631     }
2632 
2633     @Test
2634     public void testCannotInitTwice() throws PluginParseException
2635     {
2636         manager = newDefaultPluginManager();
2637         manager.init();
2638         try
2639         {
2640             manager.init();
2641             fail("IllegalStateException expected");
2642         }
2643         catch (final IllegalStateException expected)
2644         {
2645         }
2646     }
2647 
2648     @Test
2649     public void testCannotShutdownTwice() throws PluginParseException
2650     {
2651         manager = newDefaultPluginManager();
2652         manager.init();
2653         manager.shutdown();
2654         try
2655         {
2656             manager.shutdown();
2657             fail("IllegalStateException expected");
2658         }
2659         catch (final IllegalStateException expected)
2660         {
2661         }
2662     }
2663 
2664     @Test
2665     public void testGetPluginWithNullKey()
2666     {
2667         manager = newDefaultPluginManager();
2668         manager.init();
2669         try
2670         {
2671             getPluginAccessor().getPlugin(null);
2672             fail();
2673         }
2674         catch (IllegalArgumentException ex)
2675         {
2676             // test passed
2677         }
2678     }
2679 
2680     @Test
2681     public void testShutdownHandlesException()
2682     {
2683         final ThingsAreWrongListener listener = new ThingsAreWrongListener();
2684         pluginEventManager.register(listener);
2685 
2686         manager = newDefaultPluginManager();
2687         manager.init();
2688         try
2689         {
2690             //this should not throw an exception
2691             manager.shutdown();
2692         }
2693         catch (Exception e)
2694         {
2695             fail("Should not have thrown an exception!");
2696         }
2697         assertTrue(listener.isCalled());
2698     }
2699 
2700     private void writeToFile(String file, String line) throws IOException, URISyntaxException
2701     {
2702         final String resourceName = ClasspathFilePluginMetadata.class.getPackage().getName().replace(".", "/") + "/" + file;
2703         URL resource = getClass().getClassLoader().getResource(resourceName);
2704 
2705         FileOutputStream fout = new FileOutputStream(new File(resource.toURI()), false);
2706         fout.write(line.getBytes(), 0, line.length());
2707         fout.close();
2708 
2709     }
2710 
2711     @Test
2712     public void testExceptionOnRequiredPluginNotEnabling() throws Exception
2713     {
2714         try
2715         {
2716             writeToFile("application-required-modules.txt", "foo.required:bar");
2717             writeToFile("application-required-plugins.txt", "foo.required");
2718 
2719             final PluginLoader mockPluginLoader = mock(PluginLoader.class);
2720             final ModuleDescriptor<Object> badModuleDescriptor = mockFailingModuleDescriptor("foo.required:bar", FailureMode.FAIL_TO_ENABLE);
2721 
2722             final AbstractModuleDescriptor goodModuleDescriptor = mock(AbstractModuleDescriptor.class);
2723             when(goodModuleDescriptor.getKey()).thenReturn("baz");
2724             when(goodModuleDescriptor.getCompleteKey()).thenReturn("foo.required:baz");
2725 
2726             Plugin plugin = mockStaticPlugin("foo.required", goodModuleDescriptor, badModuleDescriptor);
2727 
2728             when(mockPluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(Collections.singletonList(plugin));
2729 
2730             manager = newDefaultPluginManager(mockPluginLoader);
2731             try
2732             {
2733                 manager.init();
2734             }
2735             catch(PluginException e)
2736             {
2737                 // expected
2738                 assertEquals("Unable to validate required plugins or modules", e.getMessage());
2739                 return;
2740             }
2741             fail("A PluginException is expected when trying to initialize the plugins system with required plugins that do not load.");
2742         }
2743         finally
2744         {
2745             // remove references from required files
2746             writeToFile("application-required-modules.txt", "");
2747             writeToFile("application-required-plugins.txt", "");
2748         }
2749     }
2750 
2751     @Test
2752     public void pluginReturnedByLoadAllPluginsButNotUsedIsDiscarded()
2753     {
2754         final String pluginKey = "pluginKey";
2755         final PluginPersistentStateStore pluginPersistentStateStore = mock(
2756             PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
2757         when(pluginPersistentStateStore.load().getPluginRestartState(pluginKey)).thenReturn(PluginRestartState.NONE);
2758 
2759         DiscardablePluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
2760         Plugin pluginV1 = mock(Plugin.class, RETURNS_DEEP_STUBS);
2761         Plugin pluginV2 = mock(Plugin.class, RETURNS_DEEP_STUBS);
2762         when(pluginV1.getKey()).thenReturn(pluginKey);
2763         when(pluginV2.getKey()).thenReturn(pluginKey);
2764         // Set up so that pluginV1 < pluginV2 so DefaultPluginManager should install only pluginV2
2765         when(pluginV1.compareTo(pluginV2)).thenReturn(-1);
2766         when(pluginV2.compareTo(pluginV1)).thenReturn(1);
2767         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginV1, pluginV2));
2768 
2769         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
2770             pluginPersistentStateStore,
2771             Arrays.asList((PluginLoader)pluginLoader),
2772             mock(ModuleDescriptorFactory.class),
2773             mock(PluginEventManager.class),
2774             mock(PluginExceptionInterception.class)
2775             );
2776         defaultPluginManager.init();
2777         verify(pluginLoader).discardPlugin(pluginV1);
2778     }
2779 
2780     @Test
2781     public void oldPluginReturnedByLoadFoundPluginsIsDiscarded()
2782     {
2783         final String pluginKey = "pluginKey";
2784         final PluginPersistentStateStore pluginPersistentStateStore = mock(
2785             PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
2786         when(pluginPersistentStateStore.load().getPluginRestartState(pluginKey)).thenReturn(PluginRestartState.NONE);
2787 
2788         DiscardablePluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
2789         Plugin pluginV1 = mock(Plugin.class, RETURNS_DEEP_STUBS);
2790         Plugin pluginV2 = mock(Plugin.class, RETURNS_DEEP_STUBS);
2791         when(pluginV1.getKey()).thenReturn(pluginKey);
2792         when(pluginV2.getKey()).thenReturn(pluginKey);
2793         // Set up so that pluginV1 < pluginV2 so DefaultPluginManager should install only pluginV2
2794         when(pluginV1.compareTo(pluginV2)).thenReturn(-1);
2795         when(pluginV2.compareTo(pluginV1)).thenReturn(1);
2796         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginV2));
2797         when(pluginLoader.supportsAddition()).thenReturn(true);
2798         when(pluginLoader.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginV1));
2799 
2800         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
2801             pluginPersistentStateStore,
2802             Arrays.asList((PluginLoader)pluginLoader),
2803             mock(ModuleDescriptorFactory.class),
2804             mock(PluginEventManager.class),
2805             mock(PluginExceptionInterception.class)
2806             );
2807         defaultPluginManager.init();
2808         final int found = defaultPluginManager.scanForNewPlugins();
2809         assertThat(found, is(1));
2810         verify(pluginLoader).discardPlugin(pluginV1);
2811     }
2812 
2813     @Test
2814     public void uninstallingDeletableUninstallablePluginRemovesItFromLoader()
2815     {
2816         final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
2817         final PluginLoader pluginLoader = mock(PluginLoader.class);
2818         final DefaultPluginManager defaultPluginManager = setupUninstallTest(true, true, plugin, pluginLoader);
2819         defaultPluginManager.uninstall(plugin);
2820         verify(pluginLoader).removePlugin(plugin);
2821     }
2822 
2823     @Test
2824     public void uninstallingNotDeletableUninstallablePluginRemovesItFromLoader()
2825     {
2826         final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
2827         final PluginLoader pluginLoader = mock(PluginLoader.class);
2828         final DefaultPluginManager defaultPluginManager = setupUninstallTest(false, true, plugin, pluginLoader);
2829         defaultPluginManager.uninstall(plugin);
2830         verify(pluginLoader).removePlugin(plugin);
2831     }
2832 
2833     @Test
2834     public void uninstallingDeletableNotUninstallablePluginDoesNotRemoveItFromLoader()
2835     {
2836         final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
2837         final PluginLoader pluginLoader = mock(PluginLoader.class);
2838         final DefaultPluginManager defaultPluginManager = setupUninstallTest(true, false, plugin, pluginLoader);
2839         // We want to ensure the removal is not attempted, so we make removal throw something unexpected
2840         doThrow(new AssertionError("Unexpected PluginLoader.removePlugin call")).when(pluginLoader).removePlugin(plugin);
2841         expectedException.expect(PluginException.class);
2842         expectedException.expectMessage(plugin.getKey());
2843         defaultPluginManager.uninstall(plugin);
2844     }
2845 
2846     @Test
2847     public void uninstallingNotDeletableNotUninstallablePluginDoesNotRemoveItFromLoader()
2848     {
2849         final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
2850         final PluginLoader pluginLoader = mock(PluginLoader.class);
2851         final DefaultPluginManager defaultPluginManager = setupUninstallTest(false, false, plugin, pluginLoader);
2852         // We want to ensure the removal is not attempted, so we make removal throw something unexpected
2853         doThrow(new AssertionError("Unexpected PluginLoader.removePlugin call")).when(pluginLoader).removePlugin(plugin);
2854         expectedException.expect(PluginException.class);
2855         expectedException.expectMessage(plugin.getKey());
2856         defaultPluginManager.uninstall(plugin);
2857     }
2858 
2859     private DefaultPluginManager setupUninstallTest(
2860             final boolean isDeleteable,
2861             final boolean isUninstallable,
2862             final Plugin plugin,
2863             final PluginLoader pluginLoader)
2864     {
2865         final String pluginKey = "uninstall-test-plugin-key";
2866         when(plugin.getKey()).thenReturn(pluginKey);
2867         when(plugin.isDeleteable()).thenReturn(isDeleteable);
2868         when(plugin.isUninstallable()).thenReturn(isUninstallable);
2869         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(plugin));
2870         when(pluginLoader.supportsRemoval()).thenReturn(true);
2871 
2872         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
2873                 mock(PluginPersistentStateStore.class, RETURNS_DEEP_STUBS),
2874                 Arrays.asList(pluginLoader),
2875                 mock(ModuleDescriptorFactory.class),
2876                 mock(PluginEventManager.class),
2877                 mock(PluginExceptionInterception.class)
2878         );
2879         defaultPluginManager.init();
2880         return defaultPluginManager;
2881     }
2882 
2883     @Test
2884     public void earlyLateStartupEvents()
2885     {
2886         final PluginPersistentStateStore pluginPersistentStateStore = mock(
2887                 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
2888 
2889         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
2890         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.<Plugin>asList());
2891         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
2892 
2893         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
2894 
2895         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
2896 
2897         final SplitStartupPluginSystemLifecycle splitStartupPluginSystemLifecycle = new DefaultPluginManager(
2898                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager);
2899 
2900         splitStartupPluginSystemLifecycle.earlyStartup();
2901         final ArgumentCaptor<Object> earlyEvents = ArgumentCaptor.forClass(Object.class);
2902         verify(pluginEventManager, times(2)).broadcast(earlyEvents.capture());
2903         assertThat(earlyEvents.getAllValues(), contains(
2904                 instanceOf(PluginFrameworkStartingEvent.class),instanceOf(PluginFrameworkDelayedEvent.class)));
2905 
2906         // reset() is a bit naughty, but we want to check events are sent when expected
2907         reset(pluginEventManager);
2908 
2909         splitStartupPluginSystemLifecycle.lateStartup();
2910         final ArgumentCaptor<Object> laterEvents = ArgumentCaptor.forClass(Object.class);
2911         verify(pluginEventManager, times(2)).broadcast(laterEvents.capture());
2912         assertThat(laterEvents.getAllValues(), contains(
2913                 instanceOf(PluginFrameworkResumingEvent.class), instanceOf(PluginFrameworkStartedEvent.class)));
2914     }
2915 
2916     @Test
2917     public void delayedPluginsAreDelayed()
2918     {
2919         final String earlyKey = "earlyKey";
2920         final String laterKey = "laterKey";
2921 
2922         final PluginPersistentStateStore pluginPersistentStateStore = mock(
2923                 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
2924         when(pluginPersistentStateStore.load().getPluginRestartState(earlyKey)).thenReturn(PluginRestartState.NONE);
2925         when(pluginPersistentStateStore.load().getPluginRestartState(laterKey)).thenReturn(PluginRestartState.NONE);
2926 
2927         PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
2928         Plugin earlyPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
2929         Plugin laterPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
2930         when(earlyPlugin.getKey()).thenReturn(earlyKey);
2931         when(laterPlugin.getKey()).thenReturn(laterKey);
2932         List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
2933         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
2934         List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
2935 
2936         ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
2937 
2938         PluginEventManager pluginEventManager = mock(PluginEventManager.class);
2939 
2940         PluginPredicate pluginPredicate = mock(PluginPredicate.class);
2941         when(pluginPredicate.matches(earlyPlugin)).thenReturn(false);
2942         when(pluginPredicate.matches(laterPlugin)).thenReturn(true);
2943 
2944         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
2945                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
2946 
2947         defaultPluginManager.earlyStartup();
2948         assertThat(defaultPluginManager.getPlugin(earlyKey), is(earlyPlugin));
2949         assertThat(defaultPluginManager.getPlugin(laterKey), nullValue());
2950 
2951         defaultPluginManager.lateStartup();
2952         assertThat(defaultPluginManager.getPlugin(earlyKey), is(earlyPlugin));
2953         assertThat(defaultPluginManager.getPlugin(laterKey), is(laterPlugin));
2954     }
2955 
2956     @Test
2957     public void delayedPluginsCanBeDisabled()
2958     {
2959         final String earlyKey = "earlyKey";
2960         final String laterKey = "laterKey";
2961 
2962         Wrapper wrapper = new Wrapper(earlyKey, laterKey).invoke(true);
2963         DefaultPluginManager defaultPluginManager = wrapper.getDefaultPluginManager();
2964         Plugin laterPlugin = wrapper.getLaterPlugin();
2965 
2966         defaultPluginManager.earlyStartup();
2967         defaultPluginManager.disablePlugin(laterKey);
2968 
2969         defaultPluginManager.lateStartup();
2970         verify(laterPlugin, never()).enable();
2971     }
2972 
2973     @Test
2974     public void delayedPluginsCanBeEnabled()
2975     {
2976         final String earlyKey = "earlyKey";
2977         final String laterKey = "laterKey";
2978 
2979         Wrapper wrapper = new Wrapper(earlyKey, laterKey).invoke(false);
2980         DefaultPluginManager defaultPluginManager = wrapper.getDefaultPluginManager();
2981         Plugin laterPlugin = wrapper.getLaterPlugin();
2982         defaultPluginManager.earlyStartup();
2983         defaultPluginManager.enablePlugin(laterKey);
2984 
2985         defaultPluginManager.lateStartup();
2986         verify(laterPlugin).enable();
2987     }
2988 
2989     @Test
2990     public void scanForNewPluginsNotAllowedBeforeLateStartup()
2991     {
2992         final PluginPersistentStateStore pluginPersistentStateStore = mock(
2993                 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
2994 
2995         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
2996         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.<Plugin>asList());
2997         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
2998 
2999         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
3000 
3001         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
3002 
3003         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3004                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager);
3005 
3006         defaultPluginManager.earlyStartup();
3007 
3008         expectedException.expect(IllegalStateException.class);
3009         defaultPluginManager.scanForNewPlugins();
3010     }
3011 
3012     @Test
3013     public void scanForNewPluginsDuringLateStartup()
3014     {
3015         final String pluginKey = "plugin-key";
3016         final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
3017         when(plugin.getKey()).thenReturn(pluginKey);
3018         when(plugin.isEnabledByDefault()).thenReturn(true);
3019         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
3020 
3021         final PluginPersistentStateStore pluginPersistentStateStore = mock(
3022                 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
3023         when(pluginPersistentStateStore.load().isEnabled(plugin)).thenReturn(true);
3024 
3025         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
3026 
3027         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
3028         when(pluginLoader.loadAllPlugins(moduleDescriptorFactory)).thenReturn(Arrays.asList(plugin));
3029         when(pluginLoader.loadFoundPlugins(moduleDescriptorFactory)).thenReturn(Arrays.<Plugin>asList());
3030         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
3031 
3032         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
3033 
3034         final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
3035         when(pluginPredicate.matches(plugin)).thenReturn(true);
3036 
3037         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3038                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
3039 
3040         // Set up to do the equivalent of what connect is doing in ACDEV-1276, namely responding to plugin enable with
3041         // a reentrant plugin installation.
3042         final Answer<Object> scanForNewPlugins = new Answer<Object>()
3043         {
3044             @Override
3045             public Object answer(final InvocationOnMock invocation) throws Throwable
3046             {
3047                 defaultPluginManager.scanForNewPlugins();
3048                 return null;
3049             }
3050         };
3051         doAnswer(scanForNewPlugins).when(pluginEventManager).broadcast(isA(PluginEnabledEvent.class));
3052 
3053         defaultPluginManager.earlyStartup();
3054         defaultPluginManager.lateStartup();
3055     }
3056 
3057 
3058     @Test
3059     public void earlyStartupDoesNotSavePluginPersistentState()
3060     {
3061         // Make a family of plugins for each possible persistent state
3062         final String pluginKeyPrefix = "pluginWithRestartState_";
3063         final ImmutableList.Builder<Plugin> pluginListBuilder = ImmutableList.<Plugin>builder();
3064         final PluginPersistentState.Builder pluginPersistentStateBuilder = PluginPersistentState.Builder.create();
3065         for(final PluginRestartState pluginRestartState : PluginRestartState.values())
3066         {
3067             final String pluginKey = pluginKeyPrefix + pluginRestartState.toString();
3068             final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
3069             when(plugin.getKey()).thenReturn(pluginKey);
3070             pluginListBuilder.add(plugin);
3071             pluginPersistentStateBuilder.setPluginRestartState(pluginKey, pluginRestartState);
3072 
3073         }
3074         final PluginPersistentStateStore pluginPersistentStateStore = mock(PluginPersistentStateStore.class);
3075         when(pluginPersistentStateStore.load()).thenReturn(pluginPersistentStateBuilder.toState());
3076         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
3077         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(pluginListBuilder.build());
3078         final List<PluginLoader> pluginLoaders = ImmutableList.of(pluginLoader);
3079 
3080         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
3081         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
3082         final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
3083         when(pluginPredicate.matches(any(Plugin.class))).thenReturn(false);
3084 
3085         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3086                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
3087 
3088         defaultPluginManager.earlyStartup();
3089         verify(pluginPersistentStateStore, never()).save(any(PluginPersistentState.class));
3090     }
3091 
3092     @Test
3093     public void lateStartupRemovesPluginsMarkedForRemoval()
3094     {
3095         final String earlyKey = "earlyKey";
3096         final String laterKey = "laterKey";
3097         final Plugin earlyPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
3098         final Plugin laterPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
3099         when(earlyPlugin.getKey()).thenReturn(earlyKey);
3100         when(laterPlugin.getKey()).thenReturn(laterKey);
3101 
3102         // Since we're interested in the final computed state, we use a real PluginPersistentStateStore here
3103         final PluginPersistentStateStore pluginPersistentStateStore = new MemoryPluginPersistentStateStore();
3104         pluginPersistentStateStore.save(PluginPersistentState.Builder.create()
3105                 .setEnabled(earlyPlugin, !earlyPlugin.isEnabledByDefault())
3106                 .setEnabled(laterPlugin, !laterPlugin.isEnabledByDefault())
3107                 .setPluginRestartState(earlyKey, PluginRestartState.REMOVE)
3108                 .setPluginRestartState(laterKey, PluginRestartState.REMOVE)
3109                 .toState());
3110 
3111         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
3112         final List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
3113         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
3114         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
3115 
3116         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
3117 
3118         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
3119 
3120         final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
3121         when(pluginPredicate.matches(earlyPlugin)).thenReturn(false);
3122         when(pluginPredicate.matches(laterPlugin)).thenReturn(true);
3123 
3124         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3125                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
3126 
3127         defaultPluginManager.earlyStartup();
3128         verify(pluginLoader, never()).removePlugin(any(Plugin.class));
3129         // We can't verify pluginPersistentStateStore.save, because it's not a Mock
3130 
3131         defaultPluginManager.lateStartup();
3132         verify(pluginLoader).removePlugin(earlyPlugin);
3133         verify(pluginLoader).removePlugin(laterPlugin);
3134 
3135         final PluginPersistentState pluginPersistentState = pluginPersistentStateStore.load();
3136         // Enablement should have fallen back to default state
3137         assertThat(pluginPersistentState.isEnabled(earlyPlugin), is(earlyPlugin.isEnabledByDefault()));
3138         assertThat(pluginPersistentState.isEnabled(laterPlugin), is(laterPlugin.isEnabledByDefault()));
3139         // Restart state should have fallen back to default state
3140         assertThat(pluginPersistentState.getPluginRestartState(earlyKey), is(PluginRestartState.NONE));
3141         assertThat(pluginPersistentState.getPluginRestartState(laterKey), is(PluginRestartState.NONE));
3142         // This final test is arguably a bit brittle, but i think it's valid given the documentation for getMap
3143         assertThat(pluginPersistentState.getMap().size(), is(0));
3144     }
3145 
3146     @Test
3147     public void exampleUsingPersistentStateDelegation()
3148     {
3149         final String earlyKey = "earlyKey";
3150         final String laterKey = "laterKey";
3151         final Plugin earlyPlugin = mock(Plugin.class, RETURNS_MOCKS);
3152         final Plugin laterPlugin = mock(Plugin.class, RETURNS_MOCKS);
3153         when(earlyPlugin.getKey()).thenReturn(earlyKey);
3154         when(laterPlugin.getKey()).thenReturn(laterKey);
3155         when(earlyPlugin.isEnabledByDefault()).thenReturn(true);
3156         when(laterPlugin.isEnabledByDefault()).thenReturn(true);
3157 
3158         // This is an example of using a DelegatingPluginPersistentStateStore to manage persistent state
3159         final boolean tenanted[] = { false };
3160         final PluginPersistentStateStore warmStore = new LoadOnlyPluginPersistentStateStore();
3161         final PluginPersistentStateStore tenantedStore = new MemoryPluginPersistentStateStore();
3162         final PluginPersistentStateStore pluginPersistentStateStore = new DelegatingPluginPersistentStateStore()
3163         {
3164             @Override
3165             public PluginPersistentStateStore getDelegate()
3166             {
3167                 return !tenanted[0] ? warmStore : tenantedStore;
3168             }
3169         };
3170 
3171         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
3172         final List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
3173         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
3174         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
3175 
3176         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
3177 
3178         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
3179 
3180         final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
3181         when(pluginPredicate.matches(earlyPlugin)).thenReturn(false);
3182         when(pluginPredicate.matches(laterPlugin)).thenReturn(true);
3183 
3184         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3185                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
3186 
3187         defaultPluginManager.earlyStartup();
3188 
3189         // Become tenanted
3190         tenanted[0] = true;
3191 
3192         defaultPluginManager.lateStartup();
3193 
3194         when(earlyPlugin.getPluginState()).thenReturn(PluginState.ENABLED);
3195         when(laterPlugin.getPluginState()).thenReturn(PluginState.ENABLED);
3196 
3197         // Change some things which get persisted
3198         defaultPluginManager.disablePlugin(earlyKey);
3199         defaultPluginManager.disablePlugin(laterKey);
3200         // And check they persist
3201         final PluginPersistentState pluginPersistentState = tenantedStore.load();
3202         assertThat(pluginPersistentState.isEnabled(earlyPlugin), is(false));
3203         assertThat(pluginPersistentState.isEnabled(laterPlugin), is(false));
3204         // Check that we actually persisted something, which is a good check the test isn't broken.
3205         assertThat(pluginPersistentState.getMap().size(), is(2));
3206     }
3207 
3208     @Test
3209     public void upgradePluginUpgradesPlugin()
3210     {
3211         final String pluginKey = "pluginKey";
3212         final PluginPersistentStateStore pluginPersistentStateStore = mock(
3213                 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
3214         when(pluginPersistentStateStore.load().getPluginRestartState(pluginKey)).thenReturn(PluginRestartState.NONE);
3215 
3216         Plugin pluginV1 = mock(Plugin.class, RETURNS_DEEP_STUBS);
3217         Plugin pluginV2 = mock(Plugin.class, RETURNS_DEEP_STUBS);
3218         when(pluginV1.getKey()).thenReturn(pluginKey);
3219         when(pluginV2.getKey()).thenReturn(pluginKey);
3220         when(pluginV1.isDeleteable()).thenReturn(true);
3221         when(pluginV1.isUninstallable()).thenReturn(true);
3222         // Set up so that pluginV1 < pluginV2
3223         when(pluginV1.compareTo(pluginV2)).thenReturn(-1);
3224         when(pluginV2.compareTo(pluginV1)).thenReturn(1);
3225 
3226         DiscardablePluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
3227         when(pluginLoader.supportsAddition()).thenReturn(true);
3228         when(pluginLoader.supportsRemoval()).thenReturn(true);
3229         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginV1));
3230         when(pluginLoader.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginV2));
3231 
3232         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3233                 pluginPersistentStateStore,
3234                 Arrays.asList((PluginLoader)pluginLoader),
3235                 mock(ModuleDescriptorFactory.class),
3236                 mock(PluginEventManager.class),
3237                 mock(PluginExceptionInterception.class)
3238         );
3239         defaultPluginManager.init();
3240 
3241         assertThat(defaultPluginManager.getPlugin(pluginKey), is(pluginV1));
3242 
3243         final int found = defaultPluginManager.scanForNewPlugins();
3244 
3245         assertThat(found, is(1));
3246         assertThat(defaultPluginManager.getPlugin(pluginKey), is(pluginV2));
3247 
3248         verify(pluginLoader).removePlugin(pluginV1);
3249     }
3250 
3251     @Test
3252     public void upgradePluginDisablesDependentPlugins()
3253     {
3254         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
3255 
3256         final String pluginKey = "pluginKey";
3257         final Plugin pluginV1 = mockStateChangePlugin(pluginKey, pluginEventManager);
3258         final Plugin pluginV2 = mockStateChangePlugin(pluginKey, pluginEventManager);
3259         when(pluginV1.isDeleteable()).thenReturn(true);
3260 
3261         final String dependentPluginKey = "dependentPluginKey";
3262         final Plugin dependentPlugin = mockStateChangePlugin(dependentPluginKey, pluginEventManager);
3263         when(dependentPlugin.isEnabledByDefault()).thenReturn(true);
3264         when(dependentPlugin.getRequiredPlugins()).thenReturn(ImmutableSet.of(pluginKey));
3265 
3266         // We need to compareTo to work for plugins that participate in the same addPlugins, so set a good global order
3267         mockPluginsSortOrder(pluginV1, pluginV2, dependentPlugin);
3268 
3269         final DiscardablePluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
3270         when(pluginLoader.supportsAddition()).thenReturn(true);
3271         when(pluginLoader.supportsRemoval()).thenReturn(true);
3272         final List<Plugin> initialPlugins = Arrays.asList(pluginV1, dependentPlugin);
3273         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(initialPlugins);
3274         when(pluginLoader.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginV2));
3275 
3276         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3277                 pluginPersistentStateStore,
3278                 Arrays.asList((PluginLoader)pluginLoader),
3279                 mock(ModuleDescriptorFactory.class),
3280                 mock(PluginEventManager.class),
3281                 mock(PluginExceptionInterception.class)
3282         );
3283         defaultPluginManager.init();
3284         verify(pluginV1).enable();
3285         verify(dependentPlugin).enable();
3286         when(pluginV1.getPluginState()).thenReturn(PluginState.ENABLED);
3287         when(dependentPlugin.getPluginState()).thenReturn(PluginState.ENABLED);
3288 
3289         final int found = defaultPluginManager.scanForNewPlugins();
3290         assertThat(found, is(1));
3291         verify(dependentPlugin).disable();
3292         verify(dependentPlugin, times(2)).enable();
3293     }
3294 
3295     @Test
3296     public void scanForNewPluginsScansAllPluginLoaders()
3297     {
3298         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
3299 
3300         final PluginLoader pluginLoaderAlpha = mockPluginLoaderForPlugins();
3301         when(pluginLoaderAlpha.supportsAddition()).thenReturn(true);
3302         final PluginLoader pluginLoaderBeta = mockPluginLoaderForPlugins();
3303         when(pluginLoaderBeta.supportsAddition()).thenReturn(true);
3304 
3305         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3306                 pluginPersistentStateStore,
3307                 Arrays.asList(pluginLoaderAlpha, pluginLoaderBeta),
3308                 mock(ModuleDescriptorFactory.class),
3309                 mock(PluginEventManager.class),
3310                 mock(PluginExceptionInterception.class)
3311         );
3312         defaultPluginManager.init();
3313 
3314         assertThat(defaultPluginManager.getPlugins(), empty());
3315 
3316         final Plugin pluginAlpha = mock(Plugin.class, RETURNS_DEEP_STUBS);
3317         when(pluginAlpha.getKey()).thenReturn("alpha");
3318         when(pluginLoaderAlpha.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginAlpha));
3319         final Plugin pluginBeta = mock(Plugin.class, RETURNS_DEEP_STUBS);
3320         when(pluginBeta.getKey()).thenReturn("beta");
3321         when(pluginLoaderBeta.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginBeta));
3322 
3323         final int found = defaultPluginManager.scanForNewPlugins();
3324 
3325         assertThat(found, is(2));
3326         assertThat(defaultPluginManager.getPlugins(), containsInAnyOrder(pluginAlpha, pluginBeta));
3327     }
3328 
3329     @Test
3330     public void startupElementInPluginInformationOverridesDelayPredicate()
3331     {
3332         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
3333 
3334         final String earlyKey = "earlyKey";
3335         final String laterKey = "laterKey";
3336         final Plugin earlyPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
3337         final Plugin laterPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
3338         when(earlyPlugin.getKey()).thenReturn(earlyKey);
3339         when(earlyPlugin.getPluginInformation().getStartup()).thenReturn("early");
3340         when(laterPlugin.getKey()).thenReturn(laterKey);
3341         when(laterPlugin.getPluginInformation().getStartup()).thenReturn("late");
3342 
3343         // We need to compareTo to work for plugins that participate in the same addPlugins. Without this, this test can fail for
3344         // opaque reasons (the install of laterPlugin is dropped as a duplicate). While this currently only happens when the code
3345         // is broken, it feels brittle enough to guard against.
3346         when(earlyPlugin.compareTo(laterPlugin)).thenReturn(-1);
3347         when(laterPlugin.compareTo(earlyPlugin)).thenReturn(1);
3348 
3349         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
3350         final List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
3351         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
3352         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
3353 
3354         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
3355 
3356         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
3357 
3358         final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
3359         // This predicate is inverted from what you expect, because we want to check the PluginInformation is respected
3360         when(pluginPredicate.matches(earlyPlugin)).thenReturn(true);
3361         when(pluginPredicate.matches(laterPlugin)).thenReturn(false);
3362 
3363         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3364                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
3365 
3366         defaultPluginManager.earlyStartup();
3367         verify(earlyPlugin).install();
3368         verify(laterPlugin, never()).install();
3369 
3370         defaultPluginManager.lateStartup();
3371         verify(laterPlugin).install();
3372     }
3373 
3374     @Test
3375     public void startupOverrideFileOverridesPluginInformationAndDelayPredicate() throws Exception
3376     {
3377         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
3378 
3379         final String earlyOverrideNoInfoKey = "earlyOverrideNoInfoKey";
3380         final String lateOverrideNoInfoKey = "lateOverrideNoInfoKey";
3381         final Plugin earlyOverrideNoInfoPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
3382         final Plugin lateOverrideNoInfoPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
3383         when(earlyOverrideNoInfoPlugin.getKey()).thenReturn(earlyOverrideNoInfoKey);
3384         when(lateOverrideNoInfoPlugin.getKey()).thenReturn(lateOverrideNoInfoKey);
3385 
3386         final String earlyOverrideLateInfoKey = "earlyOverrideLateInfoKey";
3387         final String lateOverrideEarlyInfoKey = "lateOverrideEarlyInfoKey";
3388         final Plugin earlyOverrideLateInfoPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
3389         final Plugin lateOverrideEarlyInfoPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
3390         when(earlyOverrideLateInfoPlugin.getKey()).thenReturn(earlyOverrideLateInfoKey);
3391         when(earlyOverrideLateInfoPlugin.getPluginInformation().getStartup()).thenReturn("late");
3392         when(lateOverrideEarlyInfoPlugin.getKey()).thenReturn(lateOverrideEarlyInfoKey);
3393         when(lateOverrideEarlyInfoPlugin.getPluginInformation().getStartup()).thenReturn("early");
3394 
3395         final List<String> overrideContents = Arrays.asList(
3396                 earlyOverrideNoInfoKey + "=early",
3397                 lateOverrideNoInfoKey + "=late",
3398                 earlyOverrideLateInfoKey + "=early",
3399                 lateOverrideEarlyInfoKey + "=late"
3400                 );
3401         writeLines(startupOverrideFile, overrideContents);
3402         System.setProperty(getStartupOverrideFileProperty(), startupOverrideFile.getPath());
3403 
3404         // Set the plugin sort order
3405         mockPluginsSortOrder(
3406                 earlyOverrideLateInfoPlugin, earlyOverrideNoInfoPlugin, lateOverrideEarlyInfoPlugin, lateOverrideNoInfoPlugin);
3407 
3408         final List<Plugin> allPlugins = Arrays.asList(
3409                 earlyOverrideNoInfoPlugin, lateOverrideNoInfoPlugin, earlyOverrideLateInfoPlugin , lateOverrideEarlyInfoPlugin);
3410 
3411         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
3412         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(allPlugins);
3413         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
3414 
3415         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
3416 
3417         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
3418 
3419         final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
3420         // This predicate is inverted when there is nothing in the information, so that the override is doing something different.
3421         // It is inverted from the information version when it is present, since we know that is taking effect (from its test),
3422         // and we want to ensure we're changing that!
3423         when(pluginPredicate.matches(earlyOverrideNoInfoPlugin)).thenReturn(true);
3424         when(pluginPredicate.matches(lateOverrideNoInfoPlugin)).thenReturn(false);
3425         when(pluginPredicate.matches(earlyOverrideLateInfoPlugin)).thenReturn(false);
3426         when(pluginPredicate.matches(lateOverrideEarlyInfoPlugin)).thenReturn(true);
3427 
3428         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3429                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
3430 
3431         defaultPluginManager.earlyStartup();
3432         verify(earlyOverrideNoInfoPlugin).install();
3433         verify(earlyOverrideLateInfoPlugin).install();
3434         verify(lateOverrideNoInfoPlugin, never()).install();
3435         verify(lateOverrideEarlyInfoPlugin, never()).install();
3436 
3437         defaultPluginManager.lateStartup();
3438         verify(lateOverrideNoInfoPlugin).install();
3439         verify(lateOverrideEarlyInfoPlugin).install();
3440     }
3441 
3442     @Test
3443     public void installEventSequencing()
3444     {
3445         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
3446 
3447         final Plugin alphaPlugin = mockStateChangePlugin("alpha", pluginEventManager);
3448         final Plugin betaPlugin = mockStateChangePlugin("beta", pluginEventManager);
3449 
3450         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
3451 
3452         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(alphaPlugin);
3453         when(pluginLoader.supportsAddition()).thenReturn(true);
3454 
3455         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3456                 pluginPersistentStateStore,
3457                 ImmutableList.of(pluginLoader),
3458                 mock(ModuleDescriptorFactory.class),
3459                 pluginEventManager,
3460                 mock(PluginExceptionInterception.class)
3461         );
3462 
3463         final ArgumentCaptor<Object> initEvents = ArgumentCaptor.forClass(Object.class);
3464         doNothing().when(pluginEventManager).broadcast(initEvents.capture());
3465 
3466         defaultPluginManager.init();
3467 
3468         assertThat(filter(initEvents.getAllValues(), PluginEventBaseClassShims.isPluginEvent()), contains(
3469                 pluginStateChange(alphaPlugin, PluginState.INSTALLED),
3470                 pluginStateChange(alphaPlugin, PluginState.ENABLED),
3471                 pluginEvent(PluginEnabledEvent.class, alphaPlugin)
3472         ));
3473 
3474         when(pluginLoader.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(betaPlugin));
3475 
3476         final ArgumentCaptor<Object> scanEvents = ArgumentCaptor.forClass(Object.class);
3477         doNothing().when(pluginEventManager).broadcast(scanEvents.capture());
3478 
3479         final int found = defaultPluginManager.scanForNewPlugins();
3480         // This is really just checking the test isn't broken
3481         assertThat(found, is(1));
3482 
3483         assertThat(filter(scanEvents.getAllValues(), PluginEventBaseClassShims.isPluginEvent()), contains(
3484                 pluginStateChange(betaPlugin, PluginState.INSTALLED),
3485                 pluginStateChange(betaPlugin, PluginState.ENABLED),
3486                 pluginEvent(PluginEnabledEvent.class, betaPlugin)
3487         ));
3488     }
3489 
3490     @Test
3491     public void enableAndDisableEventSequencing()
3492     {
3493         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
3494 
3495         final String pluginKey = "pluginKey";
3496         final Plugin plugin = mockStateChangePlugin(pluginKey, pluginEventManager);
3497 
3498         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
3499 
3500         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
3501 
3502         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3503                 pluginPersistentStateStore,
3504                 ImmutableList.of(pluginLoader),
3505                 mock(ModuleDescriptorFactory.class),
3506                 pluginEventManager,
3507                 mock(PluginExceptionInterception.class)
3508         );
3509 
3510         defaultPluginManager.init();
3511 
3512         final ArgumentCaptor<Object> disableEvents = ArgumentCaptor.forClass(Object.class);
3513         doNothing().when(pluginEventManager).broadcast(disableEvents.capture());
3514 
3515         defaultPluginManager.disablePlugin(pluginKey);
3516 
3517         assertThat(filter(disableEvents.getAllValues(), PluginEventBaseClassShims.isPluginEvent()), contains(
3518                 pluginEvent(BeforePluginDisabledEvent.class, plugin),
3519                 pluginStateChange(plugin, PluginState.DISABLED),
3520                 pluginEvent(PluginDisabledEvent.class, plugin)
3521         ));
3522 
3523         final ArgumentCaptor<Object> enableEvents = ArgumentCaptor.forClass(Object.class);
3524         doNothing().when(pluginEventManager).broadcast(enableEvents.capture());
3525 
3526         defaultPluginManager.enablePlugins(pluginKey);
3527 
3528         assertThat(filter(enableEvents.getAllValues(), PluginEventBaseClassShims.isPluginEvent()), contains(
3529                 // Yes, on 3.2.x plugins are enabled twice - in practice (when not mocked) AbstractPlugin idempotentizes the
3530                 // state change, but the event is sent twice
3531                 pluginStateChange(plugin, PluginState.ENABLED),
3532                 pluginStateChange(plugin, PluginState.ENABLED),
3533                 pluginEvent(PluginEnabledEvent.class, plugin)
3534         ));
3535     }
3536 
3537     @Test
3538     public void upgradeEventSequencing()
3539     {
3540         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
3541 
3542         final String pluginKey = "pluginKey";
3543         final Plugin pluginV1 = mockStateChangePlugin(pluginKey, pluginEventManager);
3544         final Plugin pluginV2 = mockStateChangePlugin(pluginKey, pluginEventManager);
3545 
3546         final Plugin dependentPlugin = mockStateChangePlugin("dependentPluginKey", pluginEventManager);
3547         when(dependentPlugin.getRequiredPlugins()).thenReturn(ImmutableSet.of(pluginKey));
3548 
3549         mockPluginsSortOrder(dependentPlugin, pluginV1, pluginV2);
3550 
3551         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
3552 
3553         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(pluginV1, dependentPlugin);
3554         when(pluginLoader.supportsAddition()).thenReturn(true);
3555         when(pluginLoader.supportsRemoval()).thenReturn(true);
3556 
3557         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3558                 pluginPersistentStateStore,
3559                 ImmutableList.of(pluginLoader),
3560                 mock(ModuleDescriptorFactory.class),
3561                 pluginEventManager,
3562                 mock(PluginExceptionInterception.class)
3563         );
3564 
3565         defaultPluginManager.init();
3566 
3567         when(pluginLoader.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(ImmutableList.of(pluginV2));
3568         final ArgumentCaptor<Object> events = ArgumentCaptor.forClass(Object.class);
3569         doNothing().when(pluginEventManager).broadcast(events.capture());
3570 
3571         final int found = defaultPluginManager.scanForNewPlugins();
3572         // This is really just checking the test isn't broken
3573         assertThat(found, is(1));
3574 
3575         assertThat(filter(events.getAllValues(), PluginEventBaseClassShims.isPluginEvent()), contains(
3576                 pluginEvent(BeforePluginDisabledEvent.class, dependentPlugin),
3577                 pluginStateChange(dependentPlugin, PluginState.DISABLED),
3578                 pluginEvent(PluginDisabledEvent.class, dependentPlugin),
3579                 pluginEvent(BeforePluginDisabledEvent.class, pluginV1),
3580                 pluginStateChange(pluginV1, PluginState.DISABLED),
3581                 pluginEvent(PluginDisabledEvent.class, pluginV1),
3582                 pluginStateChange(pluginV2, PluginState.INSTALLED),
3583                 pluginEvent(PluginUpgradedEvent.class, pluginV2),
3584                 pluginStateChange(pluginV2, PluginState.ENABLED),
3585                 pluginStateChange(dependentPlugin, PluginState.ENABLED),
3586                 pluginEvent(PluginEnabledEvent.class, pluginV2),
3587                 pluginEvent(PluginEnabledEvent.class, dependentPlugin)
3588         ));
3589     }
3590 
3591     @Test
3592     public void uninstallEventSequencing()
3593     {
3594         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
3595 
3596         final String pluginKey = "pluginKey";
3597         final Plugin plugin = mockStateChangePlugin(pluginKey, pluginEventManager);
3598 
3599         final Plugin dependentPlugin = mockStateChangePlugin("dependentPluginKey", pluginEventManager);
3600         when(dependentPlugin.getRequiredPlugins()).thenReturn(ImmutableSet.of(pluginKey));
3601 
3602         mockPluginsSortOrder(dependentPlugin, plugin);
3603 
3604         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
3605 
3606         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin, dependentPlugin);
3607         when(pluginLoader.supportsRemoval()).thenReturn(true);
3608 
3609         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3610                 pluginPersistentStateStore,
3611                 ImmutableList.of(pluginLoader),
3612                 mock(ModuleDescriptorFactory.class),
3613                 pluginEventManager,
3614                 mock(PluginExceptionInterception.class)
3615         );
3616 
3617         defaultPluginManager.init();
3618 
3619         final ArgumentCaptor<Object> events = ArgumentCaptor.forClass(Object.class);
3620         doNothing().when(pluginEventManager).broadcast(events.capture());
3621 
3622         defaultPluginManager.uninstall(defaultPluginManager.getPlugin(pluginKey));
3623 
3624         assertThat(filter(events.getAllValues(), PluginEventBaseClassShims.isPluginEvent()), contains(
3625                 pluginEvent(BeforePluginDisabledEvent.class, dependentPlugin),
3626                 pluginStateChange(dependentPlugin, PluginState.DISABLED),
3627                 pluginEvent(PluginDisabledEvent.class, dependentPlugin),
3628                 pluginEvent(BeforePluginDisabledEvent.class, plugin),
3629                 pluginStateChange(plugin, PluginState.DISABLED),
3630                 pluginEvent(PluginDisabledEvent.class, plugin),
3631                 pluginEvent(PluginUninstalledEvent.class, plugin)
3632         ));
3633     }
3634 
3635     private Plugin mockStateChangePlugin(final String pluginKey, final PluginEventManager pluginEventManager)
3636     {
3637         final Plugin plugin = mockPlugin(pluginKey);
3638 
3639         doAnswerPluginStateChangeWhen(plugin, PluginState.INSTALLED, pluginEventManager).install();
3640         doAnswerPluginStateChangeWhen(plugin, PluginState.ENABLED, pluginEventManager).enable();
3641         doAnswerPluginStateChangeWhen(plugin, PluginState.DISABLED, pluginEventManager).disable();
3642         return plugin;
3643     }
3644 
3645     private Plugin mockPlugin(final String pluginKey)
3646     {
3647         final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
3648         when(plugin.getKey()).thenReturn(pluginKey);
3649         when(plugin.getPluginState()).thenReturn(PluginState.UNINSTALLED);
3650         when(plugin.getModuleDescriptors()).thenReturn(ImmutableList.<ModuleDescriptor<?>>of());
3651         when(plugin.getPluginInformation().satisfiesMinJavaVersion()).thenReturn(true);
3652         when(plugin.isUninstallable()).thenReturn(true);
3653         return plugin;
3654     }
3655 
3656     /**
3657      * Obtain an answer which updates {@link Plugin#getPluginState()} and sends a marked event when called.
3658      *
3659      * @param plugin the Plugin to stub.
3660      * @param pluginState the plugin state to report
3661      * @param pluginEventManager the {@link PluginEventManager} to broadcast a {@link PluginStateMarkerEvent} via.
3662      * @return an answer which updates {@code plugin.getPluginState()} and fires a {@code PluginStateMarkerEvent}.
3663      */
3664     private Plugin doAnswerPluginStateChangeWhen(
3665             final Plugin plugin, final PluginState pluginState, final PluginEventManager pluginEventManager)
3666     {
3667         final Answer answer = new Answer()
3668         {
3669             @Override
3670             public Object answer(final InvocationOnMock invocation) throws Throwable
3671             {
3672                 when(plugin.getPluginState()).thenReturn(pluginState);
3673                 pluginEventManager.broadcast(new PluginStateMarkerEvent(plugin, pluginState));
3674                 return null;
3675             }
3676         };
3677         return doAnswer(answer).when(plugin);
3678     }
3679 
3680     /**
3681      * This is a trick to conveniently check sequencing of plugin state change functions wrt other events.
3682      */
3683     private static class PluginStateMarkerEvent
3684     {
3685         private final Plugin plugin;
3686         private final PluginState pluginState;
3687 
3688         PluginStateMarkerEvent(final Plugin plugin, final PluginState pluginState)
3689         {
3690             this.plugin = plugin;
3691             this.pluginState = pluginState;
3692         }
3693 
3694         public Plugin getPlugin()
3695         {
3696             return plugin;
3697         }
3698 
3699         public PluginState getPluginState()
3700         {
3701             return pluginState;
3702         }
3703 
3704         @Override
3705         public String toString()
3706         {
3707             return "Marker event for " + plugin + " transition to pluginState " + pluginState;
3708         }
3709     }
3710 
3711     /**
3712      * Utility functions for emulating the PluginEvent base class present in future versions of this code.
3713      */
3714     private static class PluginEventBaseClassShims
3715     {
3716         private final static Map<Object, Function<Object, Plugin>> pluginEventExtractors =
3717                 ImmutableMap.<Object, Function<Object, Plugin>>builder()
3718                         .put(BeforePluginDisabledEvent.class, new Function<Object, Plugin>()
3719                         {
3720                             @Override
3721                             public Plugin apply(final Object pluginEvent)
3722                             {
3723                                 return ((BeforePluginDisabledEvent) pluginEvent).getPlugin();
3724                             }
3725                         })
3726                         .put(PluginDisabledEvent.class, new Function<Object, Plugin>()
3727                         {
3728                             @Override
3729                             public Plugin apply(final Object pluginEvent)
3730                             {
3731                                 return ((PluginDisabledEvent) pluginEvent).getPlugin();
3732                             }
3733                         })
3734                         .put(PluginEnabledEvent.class, new Function<Object, Plugin>()
3735                         {
3736                             @Override
3737                             public Plugin apply(final Object pluginEvent)
3738                             {
3739                                 return ((PluginEnabledEvent) pluginEvent).getPlugin();
3740                             }
3741                         })
3742                         .put(PluginUninstalledEvent.class, new Function<Object, Plugin>()
3743                         {
3744                             @Override
3745                             public Plugin apply(final Object pluginEvent)
3746                             {
3747                                 return ((PluginUninstalledEvent) pluginEvent).getPlugin();
3748                             }
3749                         })
3750                         .put(PluginUpgradedEvent.class, new Function<Object, Plugin>()
3751                         {
3752                             @Override
3753                             public Plugin apply(final Object pluginEvent)
3754                             {
3755                                 return ((PluginUpgradedEvent) pluginEvent).getPlugin();
3756                             }
3757                         })
3758                         .put(PluginStateMarkerEvent.class, new Function<Object, Plugin>()
3759                         {
3760                             @Override
3761                             public Plugin apply(final Object pluginEvent)
3762                             {
3763                                 return ((PluginStateMarkerEvent) pluginEvent).getPlugin();
3764                             }
3765                         })
3766                         .build();
3767 
3768         private static Predicate<Object> isPluginEvent()
3769         {
3770             return new Predicate<Object>()
3771             {
3772                 @Override
3773                 public boolean apply(final Object pluginEvent)
3774                 {
3775                     return (null != pluginEventExtractors.get(pluginEvent.getClass()));
3776                 }
3777             };
3778         }
3779 
3780         private static Plugin getPluginFromPluginEvent(final Object pluginEvent)
3781         {
3782             return pluginEventExtractors.get(pluginEvent.getClass()).apply(pluginEvent);
3783         }
3784     }
3785 
3786     private Matcher<Object> pluginStateChange(final Plugin plugin, final PluginState pluginState)
3787     {
3788         final Matcher<Object> pluginEventMatcher = pluginEvent(PluginStateMarkerEvent.class, plugin);
3789         final Matcher<PluginState> pluginStateMatcher = is(pluginState);
3790 
3791         return new TypeSafeMatcher<Object>()
3792         {
3793             @Override
3794             protected boolean matchesSafely(final Object event)
3795             {
3796                 return pluginEventMatcher.matches(event)
3797                         // pluginEventMatcher matches, so event instanceof PluginStateMarkerEvent, so this cast is safe
3798                         && pluginStateMatcher.matches(((PluginStateMarkerEvent)event).getPluginState());
3799             }
3800 
3801             @Override
3802             public void describeTo(final Description description)
3803             {
3804                 pluginEventMatcher.describeTo(description);
3805                 description.appendText(" and .getPluginState() ");
3806                 pluginStateMatcher.describeTo(description);
3807             }
3808         };
3809     }
3810 
3811     private Matcher<Object> pluginEvent(final Class clazz, final Plugin plugin)
3812     {
3813         final Matcher<Class> classMatcher = instanceOf(clazz);
3814         final Matcher<Plugin> pluginMatcher = is(plugin);
3815         return new TypeSafeMatcher<Object>()
3816         {
3817             @Override
3818             protected boolean matchesSafely(final Object pluginEvent)
3819             {
3820                 final Plugin pluginFromEvent = PluginEventBaseClassShims.getPluginFromPluginEvent(pluginEvent);
3821                 return classMatcher.matches(pluginEvent) && pluginMatcher.matches(pluginFromEvent);
3822             }
3823 
3824             @Override
3825             public void describeTo(final Description description)
3826             {
3827                 classMatcher.describeTo(description);
3828                 description.appendText(" for which .getPlugin() ");
3829                 pluginMatcher.describeTo(description);
3830             }
3831         };
3832     }
3833 
3834     @Test
3835     public void moduleEnableDisableEventSequencing()
3836     {
3837         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
3838 
3839         final String pluginKey = "pluginKey";
3840         final Plugin plugin = mockStateChangePlugin(pluginKey, pluginEventManager);
3841 
3842         final String moduleKeyAlpha = "alpha";
3843         final ModuleDescriptor moduleAlpha = mockStateChangePluginModule(pluginKey, moduleKeyAlpha, pluginEventManager);
3844         final String moduleKeyBeta = "beta";
3845         final ModuleDescriptor moduleBeta = mockStateChangePluginModule(pluginKey, moduleKeyBeta, pluginEventManager);
3846 
3847         when(plugin.getModuleDescriptors()).thenReturn(ImmutableList.<ModuleDescriptor<?>>of(moduleAlpha, moduleBeta));
3848         when(plugin.getModuleDescriptor(moduleKeyAlpha)).thenReturn(moduleAlpha);
3849         when(plugin.getModuleDescriptor(moduleKeyBeta)).thenReturn(moduleBeta);
3850 
3851         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
3852 
3853         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
3854 
3855         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
3856                 pluginPersistentStateStore,
3857                 ImmutableList.of(pluginLoader),
3858                 mock(ModuleDescriptorFactory.class),
3859                 pluginEventManager,
3860                 mock(PluginExceptionInterception.class)
3861         );
3862 
3863         defaultPluginManager.init();
3864 
3865         final ArgumentCaptor<Object> disableEventsCaptor = ArgumentCaptor.forClass(Object.class);
3866         doNothing().when(pluginEventManager).broadcast(disableEventsCaptor.capture());
3867         final Predicate<Object> isPluginModuleEvent = PluginModuleEventBaseClassShims.isPluginModuleEvent();
3868 
3869         defaultPluginManager.disablePlugin(pluginKey);
3870 
3871         final List<Object> disableEvents = disableEventsCaptor.getAllValues();
3872         final Iterable<Object> pluginModuleDisableEvents = filter(disableEvents, isPluginModuleEvent);
3873         assertThat(pluginModuleDisableEvents, contains(
3874                 pluginModuleEvent(BeforePluginModuleDisabledEvent.class, moduleBeta),
3875                 pluginModuleStateChange(moduleBeta, false),
3876                 pluginModuleEvent(PluginModuleDisabledEvent.class, moduleBeta),
3877                 pluginModuleEvent(BeforePluginModuleDisabledEvent.class, moduleAlpha),
3878                 pluginModuleStateChange(moduleAlpha, false),
3879                 pluginModuleEvent(PluginModuleDisabledEvent.class, moduleAlpha)
3880         ));
3881 
3882         // We now want to check that the plugin events surround the module events
3883         final int pluginDisablingIndex = indexOf(disableEvents, Predicates.instanceOf(BeforePluginDisabledEvent.class));
3884         final int firstPluginModuleDisableEvent = disableEvents.indexOf(get(pluginModuleDisableEvents, 0));
3885         assertThat(firstPluginModuleDisableEvent, greaterThan(pluginDisablingIndex));
3886         final int lastPluginModuleDisableEventIndex = disableEvents.indexOf(getLast(pluginModuleDisableEvents));
3887         final int pluginDisabledIndex = indexOf(disableEvents, Predicates.instanceOf(PluginDisabledEvent.class));
3888         assertThat(lastPluginModuleDisableEventIndex, lessThan(pluginDisabledIndex));
3889 
3890         final ArgumentCaptor<Object> enableEventsCaptor = ArgumentCaptor.forClass(Object.class);
3891         doNothing().when(pluginEventManager).broadcast(enableEventsCaptor.capture());
3892 
3893         defaultPluginManager.enablePlugins(pluginKey);
3894 
3895         final List<Object> enableEvents = enableEventsCaptor.getAllValues();
3896         final Iterable<Object> pluginModuleEnableEvents = filter(enableEvents, isPluginModuleEvent);
3897         assertThat(pluginModuleEnableEvents, contains(
3898                 pluginModuleStateChange(moduleAlpha, true),
3899                 pluginModuleEvent(PluginModuleEnabledEvent.class, moduleAlpha),
3900                 pluginModuleStateChange(moduleBeta, true),
3901                 pluginModuleEvent(PluginModuleEnabledEvent.class, moduleBeta)
3902         ));
3903 
3904         // We now want to check that the plugin events surround the module events
3905         final int pluginEnablingIndex = indexOf(disableEvents, Predicates.instanceOf(BeforePluginDisabledEvent.class));
3906         final int firstPluginModuleEnableEvent = disableEvents.indexOf(get(pluginModuleDisableEvents, 0));
3907         assertThat(firstPluginModuleEnableEvent, greaterThan(pluginEnablingIndex));
3908         final int lastPluginModuleEnableEventIndex = disableEvents.indexOf(getLast(pluginModuleDisableEvents));
3909         final int pluginEnabledIndex = indexOf(disableEvents, Predicates.instanceOf(PluginDisabledEvent.class));
3910         assertThat(lastPluginModuleEnableEventIndex, lessThan(pluginEnabledIndex));
3911     }
3912 
3913     private ModuleDescriptor<?> mockStateChangePluginModule(
3914             final String pluginKey, final String moduleKey, final PluginEventManager pluginEventManager)
3915     {
3916         final ModuleDescriptor<?> module = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
3917         when(module.getPluginKey()).thenReturn(pluginKey);
3918         when(module.getCompleteKey()).thenReturn(new ModuleCompleteKey(pluginKey, moduleKey).getCompleteKey());
3919         doAnswerModuleStateChangeWhen(module, true, pluginEventManager).enabled();
3920         doAnswerModuleStateChangeWhen(module, false, pluginEventManager).disabled();
3921         return module;
3922     }
3923 
3924     /**
3925      * Obtain an answer which sends a marked event when called.
3926      *
3927      * @param module the moduleDescriptor to stub, which must also extend {@link StateAware}.
3928      * @param enabled the enabled state to report
3929      * @param pluginEventManager the {@link PluginEventManager} to broadcast a {@link PluginModuleStateMarkerEvent} via.
3930      * @return an answer which fires a {@code PluginModuleStateMarkerEvent}.
3931      */
3932     private StateAware doAnswerModuleStateChangeWhen(
3933             final ModuleDescriptor<?> module, final boolean enabled, final PluginEventManager pluginEventManager)
3934     {
3935         final Answer answer = new Answer()
3936         {
3937             @Override
3938             public Object answer(final InvocationOnMock invocation) throws Throwable
3939             {
3940                 pluginEventManager.broadcast(new PluginModuleStateMarkerEvent(module, enabled));
3941                 return null;
3942             }
3943         };
3944         return doAnswer(answer).when((StateAware) module);
3945     }
3946 
3947     private Matcher<Object> pluginModuleStateChange(final ModuleDescriptor module, final boolean enabled)
3948     {
3949         final Matcher<Object> pluginModuleEventMatcher = pluginModuleEvent(PluginModuleStateMarkerEvent.class, module);
3950         final Matcher<Boolean> pluginModuleStateMatcher = is(enabled);
3951 
3952         return new TypeSafeMatcher<Object>()
3953         {
3954             @Override
3955             protected boolean matchesSafely(final Object event)
3956             {
3957                 return pluginModuleEventMatcher.matches(event)
3958                         // pluginModuleEventMatcher matches, so event instanceof PluginModuleStateMarkerEvent, so this cast is safe
3959                         && pluginModuleStateMatcher.matches(((PluginModuleStateMarkerEvent)event).isEnabled());
3960             }
3961 
3962             @Override
3963             public void describeTo(final Description description)
3964             {
3965                 pluginModuleEventMatcher.describeTo(description);
3966                 description.appendText(" and .isEnabled() ");
3967                 pluginModuleStateMatcher.describeTo(description);
3968             }
3969         };
3970     }
3971 
3972     /**
3973      * This is a trick to conveniently check sequencing of plugin module enabled change functions wrt other events.
3974      */
3975     private static class PluginModuleStateMarkerEvent
3976     {
3977         private final ModuleDescriptor module;
3978         private final boolean enabled;
3979 
3980         PluginModuleStateMarkerEvent(final ModuleDescriptor module, final boolean enabled)
3981         {
3982             this.module = module;
3983             this.enabled = enabled;
3984         }
3985 
3986         public ModuleDescriptor getModule()
3987         {
3988             return module;
3989         }
3990 
3991         public boolean isEnabled()
3992         {
3993             return enabled;
3994         }
3995 
3996         @Override
3997         public String toString()
3998         {
3999             return "Marker event for " + module + " with enabled " + Boolean.toString(enabled);
4000         }
4001     }
4002 
4003     /**
4004      * Utility functions for emulating the PluginModuleEvent base class present in future versions of this code.
4005      */
4006     private static class PluginModuleEventBaseClassShims
4007     {
4008         private final static Map<Object, Function<Object, ModuleDescriptor>> pluginModuleEventExtractors =
4009                 ImmutableMap.<Object, Function<Object,  ModuleDescriptor>>builder()
4010                         .put(BeforePluginModuleDisabledEvent.class, new Function<Object, ModuleDescriptor>()
4011                         {
4012                             @Override
4013                             public ModuleDescriptor apply(final Object pluginModuleEvent)
4014                             {
4015                                 return ((BeforePluginModuleDisabledEvent) pluginModuleEvent).getModule();
4016                             }
4017                         })
4018                         .put(PluginModuleDisabledEvent.class, new Function<Object, ModuleDescriptor>()
4019                         {
4020                             @Override
4021                             public ModuleDescriptor apply(final Object pluginModuleEvent)
4022                             {
4023                                 return ((PluginModuleDisabledEvent) pluginModuleEvent).getModule();
4024                             }
4025                         })
4026                         .put(PluginModuleEnabledEvent.class, new Function<Object, ModuleDescriptor>()
4027                         {
4028                             @Override
4029                             public ModuleDescriptor apply(final Object pluginModuleEvent)
4030                             {
4031                                 return ((PluginModuleEnabledEvent) pluginModuleEvent).getModule();
4032                             }
4033                         })
4034                         .put(PluginModuleStateMarkerEvent.class, new Function<Object, ModuleDescriptor>()
4035                         {
4036                             @Override
4037                             public ModuleDescriptor apply(final Object pluginModuleEvent)
4038                             {
4039                                 return ((PluginModuleStateMarkerEvent) pluginModuleEvent).getModule();
4040                             }
4041                         })
4042                         .build();
4043 
4044         private static Predicate<Object> isPluginModuleEvent()
4045         {
4046             return new Predicate<Object>()
4047             {
4048                 @Override
4049                 public boolean apply(final Object pluginModuleEvent)
4050                 {
4051                     return (null != pluginModuleEventExtractors.get(pluginModuleEvent.getClass()));
4052                 }
4053             };
4054         }
4055 
4056         private static ModuleDescriptor getModuleFromPluginModuleEvent(final Object pluginModuleEvent)
4057         {
4058             return pluginModuleEventExtractors.get(pluginModuleEvent.getClass()).apply(pluginModuleEvent);
4059         }
4060     }
4061 
4062     private Matcher<Object> pluginModuleEvent(final Class clazz, final ModuleDescriptor module)
4063     {
4064         final Matcher<Class> classMatcher = instanceOf(clazz);
4065         final Matcher<ModuleDescriptor> pluginModuleMatcher = is(module);
4066         return new TypeSafeMatcher<Object>()
4067         {
4068             @Override
4069             protected boolean matchesSafely(final Object pluginModuleEvent)
4070             {
4071                 final ModuleDescriptor module = PluginModuleEventBaseClassShims.getModuleFromPluginModuleEvent(pluginModuleEvent);
4072                 return classMatcher.matches(pluginModuleEvent) && pluginModuleMatcher.matches(module);
4073             }
4074 
4075             @Override
4076             public void describeTo(final Description description)
4077             {
4078                 classMatcher.describeTo(description);
4079                 description.appendText(" for which .getModule() ");
4080                 pluginModuleMatcher.describeTo(description);
4081             }
4082         };
4083     }
4084 
4085     @Test
4086     public void shutdownEventsAreSent()
4087     {
4088         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
4089         final List<PluginLoader> pluginLoaders = Collections.emptyList();
4090         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
4091         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
4092 
4093         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
4094                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager);
4095 
4096         defaultPluginManager.init();
4097 
4098         // Make PluginEventManager#broadcast throw during shutdown to validate the exception handling in DefaultPluginManager
4099         final ArgumentCaptor<Object> shutdownEvents = ArgumentCaptor.forClass(Object.class);
4100         final NotificationException notificationException = new NotificationException(new Throwable());
4101         doThrow(notificationException).when(pluginEventManager).broadcast(shutdownEvents.capture());
4102 
4103         defaultPluginManager.shutdown();
4104 
4105         // Check that both broadcasts were attempted
4106         assertThat(shutdownEvents.getAllValues(), contains(
4107                 instanceOf(PluginFrameworkShuttingDownEvent.class), instanceOf(PluginFrameworkShutdownEvent.class)));
4108     }
4109 
4110     @Test
4111     public void lateStartupDoesntRetryEnableWhenNotRequested()
4112     {
4113         validateLateStartupRetryEnable(false);
4114     }
4115 
4116     @Test
4117     public void lateStartupDoesRetryEnableWhenRequested()
4118     {
4119         validateLateStartupRetryEnable(true);
4120     }
4121 
4122     private void validateLateStartupRetryEnable(final boolean allowEnableRetry)
4123     {
4124         // This functionality is enabled only via a system property
4125         System.setProperty(getLateStartupEnableRetryProperty(), Boolean.toString(allowEnableRetry));
4126         // An ordinary plugin which enables as expected when installed
4127         final Plugin enablesFinePlugin = mockStateChangePlugin("enablesFine", pluginEventManager);
4128         // A plugin which does not enable when enabled()d, that is, enable() to fails first time
4129         final Plugin firstEnableFailsPlugin = mockPlugin("firstEnableFails");
4130         doAnswerPluginStateChangeWhen(firstEnableFailsPlugin, PluginState.INSTALLED, pluginEventManager).install();
4131 
4132         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
4133 
4134         mockPluginsSortOrder(enablesFinePlugin, firstEnableFailsPlugin);
4135 
4136         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(enablesFinePlugin, firstEnableFailsPlugin);
4137 
4138         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
4139                 pluginPersistentStateStore,
4140                 ImmutableList.of(pluginLoader),
4141                 mock(ModuleDescriptorFactory.class),
4142                 pluginEventManager,
4143                 mock(PluginExceptionInterception.class)
4144         );
4145 
4146         defaultPluginManager.earlyStartup();
4147 
4148         verify(enablesFinePlugin).enable();
4149         verify(firstEnableFailsPlugin).enable();
4150 
4151         // Adjust the plugin so it will enable correctly now
4152         doAnswerPluginStateChangeWhen(firstEnableFailsPlugin, PluginState.ENABLED, pluginEventManager).enable();
4153 
4154         defaultPluginManager.lateStartup();
4155 
4156         // For the plugin which enabled fine, enable has not been called again
4157         verify(enablesFinePlugin).enable();
4158 
4159         // The other enable is left to the calling test method
4160 
4161         if (allowEnableRetry)
4162         {
4163             // Retry is enabled, so the plugin which didn't enable during earlyStartup should have had enable reattempted,
4164             // so it has now been called twice
4165             verify(firstEnableFailsPlugin, times(2)).enable();
4166             final String pluginString = firstEnableFailsPlugin.toString();
4167             assertThat(capturedLogging, didLogWarn("Failed to enable", "fallback", "lateStartup", pluginString));
4168             assertThat(capturedLogging, didLogWarn("fallback enabled", "lateStartup", pluginString));
4169         }
4170         else
4171         {
4172             // Retry is disabled, so still only enabled once
4173             verify(firstEnableFailsPlugin, times(1)).enable();
4174             // Currently there is not warning in this case. We might add a warning later, but if so it should not mention
4175             // "fallback" or "lateStartup": these are the strings i'm mooting as the smoking gun for this problem in the logs.
4176             assertThat(capturedLogging, not(didLogWarn("fallback", "lateStartup")));
4177         }
4178     }
4179 
4180     private void mockPluginsSortOrder(final Plugin... mockPlugins)
4181     {
4182         for (int i = 0; i < mockPlugins.length; ++i)
4183         {
4184             for (int j = 0; j < mockPlugins.length; ++j)
4185             {
4186                 // Integer.compareTo is JDK 1.7+ only, so we just implement compare directly.
4187                 when(mockPlugins[i].compareTo(mockPlugins[j])).thenReturn(i-j);
4188             }
4189         }
4190     }
4191 
4192     private PluginPersistentStateStore mockPluginPersistentStateStore()
4193     {
4194         final PluginPersistentStateStore pluginPersistentStateStore = mock(PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
4195         when(pluginPersistentStateStore.load().getPluginRestartState(anyString())).thenReturn(PluginRestartState.NONE);
4196         when(pluginPersistentStateStore.load().isEnabled(any(Plugin.class))).thenReturn(true);
4197         when(pluginPersistentStateStore.load().isEnabled(any(ModuleDescriptor.class))).thenReturn(true);
4198         return pluginPersistentStateStore;
4199     }
4200 
4201     private PluginLoader mockPluginLoaderForPlugins(final Plugin... plugins)
4202     {
4203         final PluginLoader pluginLoader = mock(PluginLoader.class);
4204         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(plugins));
4205         return pluginLoader;
4206     }
4207 
4208     public static class ThingsAreWrongListener
4209     {
4210         private volatile boolean called = false;
4211 
4212         @PluginEventListener
4213         public void onFrameworkShutdown(final PluginFrameworkShutdownEvent event)
4214         {
4215             called = true;
4216             throw new NullPointerException("AAAH!");
4217         }
4218 
4219         public boolean isCalled()
4220         {
4221             return called;
4222         }
4223     }
4224 
4225     public Plugin createPluginWithVersion(final String version)
4226     {
4227         final Plugin p = new StaticPlugin();
4228         p.setKey("test.default.plugin");
4229         final PluginInformation pInfo = p.getPluginInformation();
4230         pInfo.setVersion(version);
4231         return p;
4232     }
4233 
4234     /**
4235      * Dummy plugin loader that reports that removal is supported and returns plugins that report that they can
4236      * be uninstalled.
4237      */
4238     private static class SinglePluginLoaderWithRemoval extends SinglePluginLoader
4239     {
4240         public SinglePluginLoaderWithRemoval(final String resource)
4241         {
4242             super(resource);
4243         }
4244 
4245         public boolean supportsRemoval()
4246         {
4247 
4248             return true;
4249         }
4250 
4251         public void removePlugin(final Plugin plugin) throws PluginException
4252         {
4253             plugins = Collections.emptyList();
4254         }
4255 
4256         protected StaticPlugin getNewPlugin()
4257         {
4258             return new StaticPlugin()
4259             {
4260                 public boolean isUninstallable()
4261                 {
4262                     return true;
4263                 }
4264             };
4265         }
4266     }
4267 
4268     class NothingModuleDescriptor extends MockUnusedModuleDescriptor
4269     {
4270     }
4271 
4272     @RequiresRestart
4273     public static class RequiresRestartModuleDescriptor extends MockUnusedModuleDescriptor
4274     {
4275     }
4276 
4277     // A subclass of a module descriptor that @RequiresRestart; should inherit the annotation
4278     public static class RequiresRestartSubclassModuleDescriptor extends RequiresRestartModuleDescriptor
4279     {
4280     }
4281 
4282     private class MultiplePluginLoader implements PluginLoader
4283     {
4284         private final String[] descriptorPaths;
4285 
4286         public MultiplePluginLoader(final String... descriptorPaths)
4287         {
4288             this.descriptorPaths = descriptorPaths;
4289         }
4290 
4291         public Iterable<Plugin> loadAllPlugins(final ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
4292         {
4293             final ImmutableList.Builder<Plugin> result = ImmutableList.builder();
4294             for (final String path : descriptorPaths)
4295             {
4296                 final SinglePluginLoader loader = new SinglePluginLoader(path);
4297                 result.addAll(loader.loadAllPlugins(moduleDescriptorFactory));
4298             }
4299             return result.build();
4300         }
4301 
4302         public boolean supportsAddition()
4303         {
4304             return false;
4305         }
4306 
4307         public boolean supportsRemoval()
4308         {
4309             return false;
4310         }
4311 
4312         public Iterable<Plugin> loadFoundPlugins(final ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
4313         {
4314             throw new UnsupportedOperationException("This PluginLoader does not support addition.");
4315         }
4316 
4317         public void removePlugin(final Plugin plugin) throws PluginException
4318         {
4319             throw new UnsupportedOperationException("This PluginLoader does not support addition.");
4320         }
4321 
4322         @Override
4323         public boolean isDynamicPluginLoader()
4324         {
4325             return false;
4326         }
4327     }
4328 
4329     private static class DynamicSinglePluginLoader extends SinglePluginLoader implements PluginLoader, DynamicPluginLoader
4330     {
4331         private final AtomicBoolean canLoad = new AtomicBoolean(false);
4332 
4333         private final String key;
4334 
4335         public DynamicSinglePluginLoader(final String key, final String resource)
4336         {
4337             super(resource);
4338             this.key = key;
4339         }
4340 
4341         @Override
4342         public boolean isDynamicPluginLoader()
4343         {
4344             return true;
4345         }
4346 
4347         public String canLoad(final PluginArtifact pluginArtifact) throws PluginParseException
4348         {
4349             return canLoad.get() ? key : null;
4350         }
4351 
4352         public boolean supportsAddition()
4353         {
4354             return true;
4355         }
4356 
4357         @Override
4358         public Iterable<Plugin> loadAllPlugins(ModuleDescriptorFactory moduleDescriptorFactory)
4359         {
4360             if (canLoad.get())
4361             {
4362                 return super.loadAllPlugins(moduleDescriptorFactory);
4363             }
4364             else
4365             {
4366                 return ImmutableList.of();
4367             }
4368         }
4369 
4370         @Override
4371         public Iterable<Plugin> loadFoundPlugins(final ModuleDescriptorFactory moduleDescriptorFactory)
4372         {
4373             if (canLoad.get())
4374             {
4375                 return super.loadAllPlugins(moduleDescriptorFactory);
4376             }
4377             else
4378             {
4379                 return ImmutableList.of();
4380             }
4381         }
4382     }
4383 
4384     private static class CannotEnablePlugin extends StaticPlugin
4385     {
4386         public CannotEnablePlugin()
4387         {
4388             setKey("foo");
4389         }
4390 
4391         @Override
4392         protected PluginState enableInternal()
4393         {
4394             throw new RuntimeException("boo");
4395         }
4396 
4397         public void disabled()
4398         {
4399         }
4400     }
4401 
4402     public static class PluginModuleEnabledListener
4403     {
4404         public volatile boolean called;
4405         @PluginEventListener
4406         public void onEnable(PluginModuleEnabledEvent event)
4407         {
4408             called = true;
4409         }
4410     }
4411 
4412     public static class PluginModuleDisabledListener
4413     {
4414         public volatile boolean called;
4415         @PluginEventListener
4416         public void onDisable(PluginModuleDisabledEvent event)
4417         {
4418             called = true;
4419         }
4420     }
4421 
4422     public static class PluginDisabledListener
4423     {
4424         public volatile boolean called;
4425         @PluginEventListener
4426         public void onDisable(PluginDisabledEvent event)
4427         {
4428             called = true;
4429         }
4430     }
4431 
4432     private class Wrapper
4433     {
4434         private final String earlyKey;
4435         private final String laterKey;
4436         private Plugin laterPlugin;
4437         private DefaultPluginManager defaultPluginManager;
4438 
4439         public Wrapper(String earlyKey, String laterKey)
4440         {
4441             this.earlyKey = earlyKey;
4442             this.laterKey = laterKey;
4443         }
4444 
4445         public Plugin getLaterPlugin()
4446         {
4447             return laterPlugin;
4448         }
4449 
4450         public DefaultPluginManager getDefaultPluginManager()
4451         {
4452             return defaultPluginManager;
4453         }
4454 
4455         public Wrapper invoke(final boolean isLatePluginEnabledByDefault)
4456         {
4457             final PluginPersistentStateStore pluginPersistentStateStore = mock(
4458                     PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
4459             when(pluginPersistentStateStore.load().getPluginRestartState(earlyKey)).thenReturn(PluginRestartState.NONE);
4460             when(pluginPersistentStateStore.load().getPluginRestartState(laterKey)).thenReturn(PluginRestartState.NONE);
4461             doAnswer(new Answer<Void>()
4462             {
4463                 @Override
4464                 public Void answer(InvocationOnMock invocationOnMock) throws Throwable
4465                 {
4466                     final PluginPersistentState pluginState = (PluginPersistentState) invocationOnMock.getArguments()[0];
4467                     when(pluginPersistentStateStore.load()).thenReturn(pluginState);
4468                     return null;
4469                 }
4470             }).when(pluginPersistentStateStore).save(isA(PluginPersistentState.class));
4471 
4472             PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
4473             Plugin earlyPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
4474             laterPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
4475             when(earlyPlugin.getKey()).thenReturn(earlyKey);
4476             when(laterPlugin.getKey()).thenReturn(laterKey);
4477             when(earlyPlugin.isEnabledByDefault()).thenReturn(true);
4478             when(laterPlugin.isEnabledByDefault()).thenReturn(isLatePluginEnabledByDefault);
4479             List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
4480             when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
4481             List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
4482 
4483             ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
4484 
4485             PluginEventManager pluginEventManager = mock(PluginEventManager.class);
4486 
4487             PluginPredicate pluginPredicate = mock(PluginPredicate.class);
4488             when(pluginPredicate.matches(earlyPlugin)).thenReturn(false);
4489             when(pluginPredicate.matches(laterPlugin)).thenReturn(true);
4490 
4491             defaultPluginManager = new DefaultPluginManager(
4492                     pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
4493             return this;
4494         }
4495     }
4496 }