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