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