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