View Javadoc
1   package com.atlassian.plugin.manager;
2   
3   import com.atlassian.plugin.DefaultModuleDescriptorFactory;
4   import com.atlassian.plugin.ModuleDescriptor;
5   import com.atlassian.plugin.ModuleDescriptorFactory;
6   import com.atlassian.plugin.Plugin;
7   import com.atlassian.plugin.PluginException;
8   import com.atlassian.plugin.PluginParseException;
9   import com.atlassian.plugin.PluginRestartState;
10  import com.atlassian.plugin.PluginState;
11  import com.atlassian.plugin.SplitStartupPluginSystemLifecycle;
12  import com.atlassian.plugin.descriptors.AbstractModuleDescriptor;
13  import com.atlassian.plugin.event.PluginEventListener;
14  import com.atlassian.plugin.event.PluginEventManager;
15  import com.atlassian.plugin.event.events.PluginEnabledEvent;
16  import com.atlassian.plugin.event.events.PluginFrameworkDelayedEvent;
17  import com.atlassian.plugin.event.events.PluginFrameworkResumingEvent;
18  import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
19  import com.atlassian.plugin.event.events.PluginFrameworkStartedEvent;
20  import com.atlassian.plugin.event.events.PluginFrameworkStartingEvent;
21  import com.atlassian.plugin.event.impl.DefaultPluginEventManager;
22  import com.atlassian.plugin.exception.PluginExceptionInterception;
23  import com.atlassian.plugin.hostcontainer.DefaultHostContainer;
24  import com.atlassian.plugin.loaders.DiscardablePluginLoader;
25  import com.atlassian.plugin.loaders.PluginLoader;
26  import com.atlassian.plugin.manager.store.DelegatingPluginPersistentStateStore;
27  import com.atlassian.plugin.manager.store.LoadOnlyPluginPersistentStateStore;
28  import com.atlassian.plugin.manager.store.MemoryPluginPersistentStateStore;
29  import com.atlassian.plugin.metadata.ClasspathFilePluginMetadata;
30  import com.atlassian.plugin.predicate.PluginPredicate;
31  import com.atlassian.plugin.test.CapturedLogging;
32  import com.google.common.collect.ImmutableList;
33  import org.junit.After;
34  import org.junit.Before;
35  import org.junit.Rule;
36  import org.junit.Test;
37  import org.junit.contrib.java.lang.system.RestoreSystemProperties;
38  import org.junit.rules.ExpectedException;
39  import org.mockito.ArgumentCaptor;
40  import org.mockito.invocation.InvocationOnMock;
41  import org.mockito.stubbing.Answer;
42  
43  import java.io.File;
44  import java.io.FileOutputStream;
45  import java.io.IOException;
46  import java.net.URISyntaxException;
47  import java.net.URL;
48  import java.util.Arrays;
49  import java.util.Collections;
50  import java.util.List;
51  
52  import static com.atlassian.plugin.manager.DefaultPluginManager.getLateStartupEnableRetryProperty;
53  import static com.atlassian.plugin.manager.DefaultPluginManager.getMinimumPluginVersionsFileProperty;
54  import static com.atlassian.plugin.manager.DefaultPluginManager.getStartupOverrideFileProperty;
55  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.FailureMode.FAIL_TO_ENABLE;
56  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.doAnswerPluginStateChangeWhen;
57  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockFailingModuleDescriptor;
58  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockPlugin;
59  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockPluginLoaderForPlugins;
60  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockPluginPersistentStateStore;
61  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockPluginWithVersion;
62  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockPluginsSortOrder;
63  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockStateChangePlugin;
64  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockStaticPlugin;
65  import static com.atlassian.plugin.test.CapturedLogging.didLogInfo;
66  import static com.atlassian.plugin.test.CapturedLogging.didLogWarn;
67  import static com.atlassian.plugin.test.PluginTestUtils.createTempDirectory;
68  import static com.google.common.collect.ImmutableList.copyOf;
69  import static org.apache.commons.io.FileUtils.deleteQuietly;
70  import static org.apache.commons.io.FileUtils.writeLines;
71  import static org.hamcrest.MatcherAssert.assertThat;
72  import static org.hamcrest.Matchers.contains;
73  import static org.hamcrest.Matchers.instanceOf;
74  import static org.hamcrest.Matchers.is;
75  import static org.hamcrest.Matchers.not;
76  import static org.hamcrest.Matchers.nullValue;
77  import static org.junit.Assert.assertEquals;
78  import static org.junit.Assert.assertTrue;
79  import static org.junit.Assert.fail;
80  import static org.mockito.Matchers.any;
81  import static org.mockito.Matchers.isA;
82  import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
83  import static org.mockito.Mockito.RETURNS_MOCKS;
84  import static org.mockito.Mockito.doAnswer;
85  import static org.mockito.Mockito.mock;
86  import static org.mockito.Mockito.never;
87  import static org.mockito.Mockito.reset;
88  import static org.mockito.Mockito.times;
89  import static org.mockito.Mockito.verify;
90  import static org.mockito.Mockito.when;
91  
92  public class TestDefaultPluginManagerLifecycle {
93      @Rule
94      public CapturedLogging capturedLogging = new CapturedLogging(DefaultPluginManager.class);
95  
96      @Rule
97      public ExpectedException expectedException = ExpectedException.none();
98  
99      @Rule
100     public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(
101             getStartupOverrideFileProperty(),
102             getLateStartupEnableRetryProperty(),
103             getMinimumPluginVersionsFileProperty());
104 
105     private File temporaryDirectory;
106     private File startupOverrideFile;
107 
108     /**
109      * the object being tested
110      */
111     private SplitStartupPluginSystemLifecycle manager;
112 
113     private PluginEventManager pluginEventManager;
114 
115     private ModuleDescriptorFactory descriptorFactory = new DefaultModuleDescriptorFactory(new DefaultHostContainer());
116 
117     private DefaultPluginManager newDefaultPluginManager(PluginLoader... pluginLoaders) {
118         DefaultPluginManager dpm = DefaultPluginManager.newBuilder().
119                 withPluginLoaders(copyOf(pluginLoaders))
120                 .withModuleDescriptorFactory(descriptorFactory)
121                 .withPluginEventManager(pluginEventManager)
122                 .withStore(new MemoryPluginPersistentStateStore())
123                 .withVerifyRequiredPlugins(true)
124                 .build();
125         manager = dpm;
126         return dpm;
127     }
128 
129     @Before
130     public void setUp() throws Exception {
131         pluginEventManager = new DefaultPluginEventManager();
132         temporaryDirectory = createTempDirectory(TestDefaultPluginManager.class);
133         startupOverrideFile = new File(temporaryDirectory, "startupOverride.properties");
134     }
135 
136     @After
137     public void tearDown() throws Exception {
138         deleteQuietly(temporaryDirectory);
139     }
140 
141 
142     @Test
143     public void delayedPluginsCanBeDisabled() {
144         final String earlyKey = "earlyKey";
145         final String laterKey = "laterKey";
146 
147         Wrapper wrapper = new Wrapper(earlyKey, laterKey).invoke(true);
148         DefaultPluginManager defaultPluginManager = wrapper.getDefaultPluginManager();
149         Plugin laterPlugin = wrapper.getLaterPlugin();
150 
151         defaultPluginManager.earlyStartup();
152         defaultPluginManager.disablePlugin(laterKey);
153 
154         defaultPluginManager.lateStartup();
155         verify(laterPlugin, never()).enable();
156     }
157 
158     @Test
159     public void delayedPluginsCanBeEnabled() {
160         final String earlyKey = "earlyKey";
161         final String laterKey = "laterKey";
162 
163         Wrapper wrapper = new Wrapper(earlyKey, laterKey).invoke(false);
164         DefaultPluginManager defaultPluginManager = wrapper.getDefaultPluginManager();
165         Plugin laterPlugin = wrapper.getLaterPlugin();
166         defaultPluginManager.earlyStartup();
167         defaultPluginManager.enablePlugin(laterKey);
168 
169         defaultPluginManager.lateStartup();
170         verify(laterPlugin).enable();
171     }
172 
173     private class Wrapper {
174         private final String earlyKey;
175         private final String laterKey;
176         private Plugin laterPlugin;
177         private DefaultPluginManager defaultPluginManager;
178 
179         public Wrapper(String earlyKey, String laterKey) {
180             this.earlyKey = earlyKey;
181             this.laterKey = laterKey;
182         }
183 
184         public Plugin getLaterPlugin() {
185             return laterPlugin;
186         }
187 
188         public DefaultPluginManager getDefaultPluginManager() {
189             return defaultPluginManager;
190         }
191 
192         public Wrapper invoke(final boolean isLatePluginEnabledByDefault) {
193             final PluginPersistentStateStore pluginPersistentStateStore = mock(
194                     PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
195             when(pluginPersistentStateStore.load().getPluginRestartState(earlyKey)).thenReturn(PluginRestartState.NONE);
196             when(pluginPersistentStateStore.load().getPluginRestartState(laterKey)).thenReturn(PluginRestartState.NONE);
197             doAnswer(new Answer<Void>() {
198                 @Override
199                 public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
200                     final PluginPersistentState pluginState = (PluginPersistentState) invocationOnMock.getArguments()[0];
201                     when(pluginPersistentStateStore.load()).thenReturn(pluginState);
202                     return null;
203                 }
204             }).when(pluginPersistentStateStore).save(isA(PluginPersistentState.class));
205 
206             PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
207             Plugin earlyPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
208             laterPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
209             when(earlyPlugin.getKey()).thenReturn(earlyKey);
210             when(laterPlugin.getKey()).thenReturn(laterKey);
211             when(earlyPlugin.isEnabledByDefault()).thenReturn(true);
212             when(laterPlugin.isEnabledByDefault()).thenReturn(isLatePluginEnabledByDefault);
213             List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
214             when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
215             List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
216 
217             ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
218 
219             PluginEventManager pluginEventManager = mock(PluginEventManager.class);
220 
221             PluginPredicate pluginPredicate = mock(PluginPredicate.class);
222             when(pluginPredicate.matches(earlyPlugin)).thenReturn(false);
223             when(pluginPredicate.matches(laterPlugin)).thenReturn(true);
224 
225             defaultPluginManager = new DefaultPluginManager(
226                     pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
227             return this;
228         }
229     }
230 
231     @Test
232     public void scanForNewPluginsNotAllowedBeforeLateStartup() {
233         final PluginPersistentStateStore pluginPersistentStateStore = mock(
234                 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
235 
236         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
237         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.<Plugin>asList());
238         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
239 
240         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
241 
242         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
243 
244         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
245                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager);
246 
247         defaultPluginManager.earlyStartup();
248 
249         expectedException.expect(IllegalStateException.class);
250         defaultPluginManager.scanForNewPlugins();
251     }
252 
253     @Test
254     public void scanForNewPluginsDuringLateStartup() {
255         final String pluginKey = "plugin-key";
256         final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
257         when(plugin.getKey()).thenReturn(pluginKey);
258         when(plugin.isEnabledByDefault()).thenReturn(true);
259         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
260 
261         final PluginPersistentStateStore pluginPersistentStateStore = mock(
262                 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
263         when(pluginPersistentStateStore.load().isEnabled(plugin)).thenReturn(true);
264 
265         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
266 
267         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
268         when(pluginLoader.loadAllPlugins(moduleDescriptorFactory)).thenReturn(Arrays.asList(plugin));
269         when(pluginLoader.loadFoundPlugins(moduleDescriptorFactory)).thenReturn(Arrays.<Plugin>asList());
270         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
271 
272         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
273 
274         final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
275         when(pluginPredicate.matches(plugin)).thenReturn(true);
276 
277         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
278                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
279 
280         // Set up to do the equivalent of what connect is doing in ACDEV-1276, namely responding to plugin enable with
281         // a reentrant plugin installation.
282         final Answer<Object> scanForNewPlugins = new Answer<Object>() {
283             @Override
284             public Object answer(final InvocationOnMock invocation) throws Throwable {
285                 defaultPluginManager.scanForNewPlugins();
286                 return null;
287             }
288         };
289         doAnswer(scanForNewPlugins).when(pluginEventManager).broadcast(isA(PluginEnabledEvent.class));
290 
291         defaultPluginManager.earlyStartup();
292         defaultPluginManager.lateStartup();
293     }
294 
295 
296     @Test
297     public void earlyStartupDoesNotSavePluginPersistentState() {
298         // Make a family of plugins for each possible persistent state
299         final String pluginKeyPrefix = "pluginWithRestartState_";
300         final ImmutableList.Builder<Plugin> pluginListBuilder = ImmutableList.<Plugin>builder();
301         final PluginPersistentState.Builder pluginPersistentStateBuilder = PluginPersistentState.Builder.create();
302         for (final PluginRestartState pluginRestartState : PluginRestartState.values()) {
303             final String pluginKey = pluginKeyPrefix + pluginRestartState.toString();
304             final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
305             when(plugin.getKey()).thenReturn(pluginKey);
306             pluginListBuilder.add(plugin);
307             pluginPersistentStateBuilder.setPluginRestartState(pluginKey, pluginRestartState);
308 
309         }
310         final PluginPersistentStateStore pluginPersistentStateStore = mock(PluginPersistentStateStore.class);
311         when(pluginPersistentStateStore.load()).thenReturn(pluginPersistentStateBuilder.toState());
312         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
313         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(pluginListBuilder.build());
314         final List<PluginLoader> pluginLoaders = ImmutableList.of(pluginLoader);
315 
316         final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
317         when(pluginPredicate.matches(any(Plugin.class))).thenReturn(false);
318 
319         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
320 
321         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
322 
323         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
324                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
325 
326         defaultPluginManager.earlyStartup();
327         verify(pluginPersistentStateStore, never()).save(any(PluginPersistentState.class));
328     }
329 
330     @Test
331     public void lateStartupRemovesPluginsMarkedForRemoval() {
332         final String earlyKey = "earlyKey";
333         final String laterKey = "laterKey";
334         final Plugin earlyPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
335         final Plugin laterPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
336         when(earlyPlugin.getKey()).thenReturn(earlyKey);
337         when(laterPlugin.getKey()).thenReturn(laterKey);
338 
339         // Since we're interested in the final computed state, we use a real PluginPersistentStateStore here
340         final PluginPersistentStateStore pluginPersistentStateStore = new MemoryPluginPersistentStateStore();
341         pluginPersistentStateStore.save(PluginPersistentState.Builder.create()
342                 .setEnabled(earlyPlugin, !earlyPlugin.isEnabledByDefault())
343                 .setEnabled(laterPlugin, !laterPlugin.isEnabledByDefault())
344                 .setPluginRestartState(earlyKey, PluginRestartState.REMOVE)
345                 .setPluginRestartState(laterKey, PluginRestartState.REMOVE)
346                 .toState());
347 
348         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
349         final List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
350         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
351         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
352 
353         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
354 
355         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
356 
357         final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
358         when(pluginPredicate.matches(earlyPlugin)).thenReturn(false);
359         when(pluginPredicate.matches(laterPlugin)).thenReturn(true);
360 
361         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
362                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
363 
364         defaultPluginManager.earlyStartup();
365         verify(pluginLoader, never()).removePlugin(any(Plugin.class));
366         // We can't verify pluginPersistentStateStore.save, because it's not a Mock
367 
368         defaultPluginManager.lateStartup();
369         verify(pluginLoader).removePlugin(earlyPlugin);
370         verify(pluginLoader).removePlugin(laterPlugin);
371 
372         final PluginPersistentState pluginPersistentState = pluginPersistentStateStore.load();
373         // Enablement should have fallen back to default state
374         assertThat(pluginPersistentState.isEnabled(earlyPlugin), is(earlyPlugin.isEnabledByDefault()));
375         assertThat(pluginPersistentState.isEnabled(laterPlugin), is(laterPlugin.isEnabledByDefault()));
376         // Restart state should have fallen back to default state
377         assertThat(pluginPersistentState.getPluginRestartState(earlyKey), is(PluginRestartState.NONE));
378         assertThat(pluginPersistentState.getPluginRestartState(laterKey), is(PluginRestartState.NONE));
379         // This final test is arguably a bit brittle, but i think it's valid given the documentation for getMap
380         assertThat(pluginPersistentState.getMap().size(), is(0));
381     }
382 
383     @Test
384     public void exampleUsingPersistentStateDelegation() {
385         final String earlyKey = "earlyKey";
386         final String laterKey = "laterKey";
387         final Plugin earlyPlugin = mock(Plugin.class, RETURNS_MOCKS);
388         final Plugin laterPlugin = mock(Plugin.class, RETURNS_MOCKS);
389         when(earlyPlugin.getKey()).thenReturn(earlyKey);
390         when(laterPlugin.getKey()).thenReturn(laterKey);
391         when(earlyPlugin.isEnabledByDefault()).thenReturn(true);
392         when(laterPlugin.isEnabledByDefault()).thenReturn(true);
393 
394         // This is an example of using a DelegatingPluginPersistentStateStore to manage persistent state
395         final boolean tenanted[] = {false};
396         final PluginPersistentStateStore warmStore = new LoadOnlyPluginPersistentStateStore();
397         final PluginPersistentStateStore tenantedStore = new MemoryPluginPersistentStateStore();
398         final PluginPersistentStateStore pluginPersistentStateStore = new DelegatingPluginPersistentStateStore() {
399             @Override
400             public PluginPersistentStateStore getDelegate() {
401                 return !tenanted[0] ? warmStore : tenantedStore;
402             }
403         };
404 
405         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
406         final List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
407         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
408         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
409 
410         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
411 
412         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
413 
414         final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
415         when(pluginPredicate.matches(earlyPlugin)).thenReturn(false);
416         when(pluginPredicate.matches(laterPlugin)).thenReturn(true);
417 
418         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
419                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
420 
421         defaultPluginManager.earlyStartup();
422 
423         // Become tenanted
424         tenanted[0] = true;
425 
426         defaultPluginManager.lateStartup();
427 
428         when(earlyPlugin.getPluginState()).thenReturn(PluginState.ENABLED);
429         when(laterPlugin.getPluginState()).thenReturn(PluginState.ENABLED);
430 
431         // Change some things which get persisted
432         defaultPluginManager.disablePlugin(earlyKey);
433         defaultPluginManager.disablePlugin(laterKey);
434         // And check they persist
435         final PluginPersistentState pluginPersistentState = tenantedStore.load();
436         assertThat(pluginPersistentState.isEnabled(earlyPlugin), is(false));
437         assertThat(pluginPersistentState.isEnabled(laterPlugin), is(false));
438         // Check that we actually persisted something, which is a good check the test isn't broken.
439         assertThat(pluginPersistentState.getMap().size(), is(2));
440     }
441 
442     @Test
443     public void lateStartupDoesntRetryEnableWhenNotRequested() {
444         validateLateStartupRetryEnable(false);
445     }
446 
447     @Test
448     public void lateStartupDoesRetryEnableWhenRequested() {
449         validateLateStartupRetryEnable(true);
450     }
451 
452     private void validateLateStartupRetryEnable(final boolean allowEnableRetry) {
453         // This functionality is enabled only via a system property
454         System.setProperty(getLateStartupEnableRetryProperty(), Boolean.toString(allowEnableRetry));
455         // An ordinary plugin which enables as expected when installed
456         final Plugin enablesFinePlugin = mockStateChangePlugin("enablesFine", pluginEventManager);
457         // A plugin which does not enable when enabled()d, that is, enable() to fails first time
458         final Plugin firstEnableFailsPlugin = mockPlugin("firstEnableFails");
459         doAnswerPluginStateChangeWhen(firstEnableFailsPlugin, PluginState.INSTALLED, pluginEventManager).install();
460 
461         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
462 
463         mockPluginsSortOrder(enablesFinePlugin, firstEnableFailsPlugin);
464 
465         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(enablesFinePlugin, firstEnableFailsPlugin);
466 
467         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
468                 pluginPersistentStateStore,
469                 ImmutableList.of(pluginLoader),
470                 mock(ModuleDescriptorFactory.class),
471                 pluginEventManager,
472                 mock(PluginExceptionInterception.class)
473         );
474 
475         defaultPluginManager.earlyStartup();
476 
477         verify(enablesFinePlugin).enable();
478         verify(firstEnableFailsPlugin).enable();
479 
480         // Adjust the plugin so it will enable correctly now
481         doAnswerPluginStateChangeWhen(firstEnableFailsPlugin, PluginState.ENABLED, pluginEventManager).enable();
482 
483         defaultPluginManager.lateStartup();
484 
485         // For the plugin which enabled fine, enable has not been called again
486         verify(enablesFinePlugin).enable();
487 
488         // The other enable is left to the calling test method
489 
490         if (allowEnableRetry) {
491             // Retry is enabled, so the plugin which didn't enable during earlyStartup should have had enable reattempted,
492             // so it has now been called twice
493             verify(firstEnableFailsPlugin, times(2)).enable();
494             final String pluginString = firstEnableFailsPlugin.toString();
495             assertThat(capturedLogging, didLogWarn("Failed to enable", "fallback", "lateStartup", pluginString));
496             assertThat(capturedLogging, didLogWarn("fallback enabled", "lateStartup", pluginString));
497         } else {
498             // Retry is disabled, so still only enabled once
499             verify(firstEnableFailsPlugin, times(1)).enable();
500             // Currently there is not warning in this case. We might add a warning later, but if so it should not mention
501             // "fallback" or "lateStartup": these are the strings i'm mooting as the smoking gun for this problem in the logs.
502             assertThat(capturedLogging, not(didLogWarn("fallback", "lateStartup")));
503         }
504     }
505 
506     @Test
507     public void startupOverrideFileOverridesPluginInformationAndDelayPredicate() throws Exception {
508         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
509 
510         final String earlyOverrideNoInfoKey = "earlyOverrideNoInfoKey";
511         final String lateOverrideNoInfoKey = "lateOverrideNoInfoKey";
512         final Plugin earlyOverrideNoInfoPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
513         final Plugin lateOverrideNoInfoPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
514         when(earlyOverrideNoInfoPlugin.getKey()).thenReturn(earlyOverrideNoInfoKey);
515         when(lateOverrideNoInfoPlugin.getKey()).thenReturn(lateOverrideNoInfoKey);
516 
517         final String earlyOverrideLateInfoKey = "earlyOverrideLateInfoKey";
518         final String lateOverrideEarlyInfoKey = "lateOverrideEarlyInfoKey";
519         final Plugin earlyOverrideLateInfoPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
520         final Plugin lateOverrideEarlyInfoPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
521         when(earlyOverrideLateInfoPlugin.getKey()).thenReturn(earlyOverrideLateInfoKey);
522         when(earlyOverrideLateInfoPlugin.getPluginInformation().getStartup()).thenReturn("late");
523         when(lateOverrideEarlyInfoPlugin.getKey()).thenReturn(lateOverrideEarlyInfoKey);
524         when(lateOverrideEarlyInfoPlugin.getPluginInformation().getStartup()).thenReturn("early");
525 
526         final List<String> overrideContents = Arrays.asList(
527                 earlyOverrideNoInfoKey + "=early",
528                 lateOverrideNoInfoKey + "=late",
529                 earlyOverrideLateInfoKey + "=early",
530                 lateOverrideEarlyInfoKey + "=late"
531         );
532         writeLines(startupOverrideFile, overrideContents);
533         System.setProperty(getStartupOverrideFileProperty(), startupOverrideFile.getPath());
534 
535         // Set the plugin sort order
536         mockPluginsSortOrder(
537                 earlyOverrideLateInfoPlugin, earlyOverrideNoInfoPlugin, lateOverrideEarlyInfoPlugin, lateOverrideNoInfoPlugin);
538 
539         final List<Plugin> allPlugins = Arrays.asList(
540                 earlyOverrideNoInfoPlugin, lateOverrideNoInfoPlugin, earlyOverrideLateInfoPlugin, lateOverrideEarlyInfoPlugin);
541 
542         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
543         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(allPlugins);
544         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
545 
546         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
547 
548         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
549 
550         final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
551         // This predicate is inverted when there is nothing in the information, so that the override is doing something different.
552         // It is inverted from the information version when it is present, since we know that is taking effect (from its test),
553         // and we want to ensure we're changing that!
554         when(pluginPredicate.matches(earlyOverrideNoInfoPlugin)).thenReturn(true);
555         when(pluginPredicate.matches(lateOverrideNoInfoPlugin)).thenReturn(false);
556         when(pluginPredicate.matches(earlyOverrideLateInfoPlugin)).thenReturn(false);
557         when(pluginPredicate.matches(lateOverrideEarlyInfoPlugin)).thenReturn(true);
558 
559         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
560                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
561 
562         defaultPluginManager.earlyStartup();
563         verify(earlyOverrideNoInfoPlugin).install();
564         verify(earlyOverrideLateInfoPlugin).install();
565         verify(lateOverrideNoInfoPlugin, never()).install();
566         verify(lateOverrideEarlyInfoPlugin, never()).install();
567 
568         defaultPluginManager.lateStartup();
569         verify(lateOverrideNoInfoPlugin).install();
570         verify(lateOverrideEarlyInfoPlugin).install();
571     }
572 
573     @Test
574     public void earlyLateStartupEvents() {
575         final PluginPersistentStateStore pluginPersistentStateStore = mock(
576                 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
577 
578         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
579         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.<Plugin>asList());
580         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
581 
582         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
583 
584         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
585 
586         final SplitStartupPluginSystemLifecycle splitStartupPluginSystemLifecycle = new DefaultPluginManager(
587                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager);
588 
589         splitStartupPluginSystemLifecycle.earlyStartup();
590         final ArgumentCaptor<Object> earlyEvents = ArgumentCaptor.forClass(Object.class);
591         verify(pluginEventManager, times(2)).broadcast(earlyEvents.capture());
592         assertThat(earlyEvents.getAllValues(), contains(
593                 instanceOf(PluginFrameworkStartingEvent.class), instanceOf(PluginFrameworkDelayedEvent.class)));
594 
595         // reset() is a bit naughty, but we want to check events are sent when expected
596         reset(pluginEventManager);
597 
598         splitStartupPluginSystemLifecycle.lateStartup();
599         final ArgumentCaptor<Object> laterEvents = ArgumentCaptor.forClass(Object.class);
600         verify(pluginEventManager, times(2)).broadcast(laterEvents.capture());
601         assertThat(laterEvents.getAllValues(), contains(
602                 instanceOf(PluginFrameworkResumingEvent.class), instanceOf(PluginFrameworkStartedEvent.class)));
603     }
604 
605     @Test
606     public void startupElementInPluginInformationOverridesDelayPredicate() {
607         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
608 
609         final String earlyKey = "earlyKey";
610         final String laterKey = "laterKey";
611         final Plugin earlyPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
612         final Plugin laterPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
613         when(earlyPlugin.getKey()).thenReturn(earlyKey);
614         when(earlyPlugin.getPluginInformation().getStartup()).thenReturn("early");
615         when(laterPlugin.getKey()).thenReturn(laterKey);
616         when(laterPlugin.getPluginInformation().getStartup()).thenReturn("late");
617 
618         // We need to compareTo to work for plugins that participate in the same addPlugins. Without this, this test can fail for
619         // opaque reasons (the install of laterPlugin is dropped as a duplicate). While this currently only happens when the code
620         // is broken, it feels brittle enough to guard against.
621         when(earlyPlugin.compareTo(laterPlugin)).thenReturn(-1);
622         when(laterPlugin.compareTo(earlyPlugin)).thenReturn(1);
623 
624         final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
625         final List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
626         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
627         final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
628 
629         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
630 
631         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
632 
633         final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
634         // This predicate is inverted from what you expect, because we want to check the PluginInformation is respected
635         when(pluginPredicate.matches(earlyPlugin)).thenReturn(true);
636         when(pluginPredicate.matches(laterPlugin)).thenReturn(false);
637 
638         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
639                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
640 
641         defaultPluginManager.earlyStartup();
642         verify(earlyPlugin).install();
643         verify(laterPlugin, never()).install();
644 
645         defaultPluginManager.lateStartup();
646         verify(laterPlugin).install();
647     }
648 
649     @Test
650     public void delayedPluginsAreDelayed() {
651         final String earlyKey = "earlyKey";
652         final String laterKey = "laterKey";
653 
654         final PluginPersistentStateStore pluginPersistentStateStore = mock(
655                 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
656         when(pluginPersistentStateStore.load().getPluginRestartState(earlyKey)).thenReturn(PluginRestartState.NONE);
657         when(pluginPersistentStateStore.load().getPluginRestartState(laterKey)).thenReturn(PluginRestartState.NONE);
658 
659         PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
660         Plugin earlyPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
661         Plugin laterPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
662         when(earlyPlugin.getKey()).thenReturn(earlyKey);
663         when(laterPlugin.getKey()).thenReturn(laterKey);
664         List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
665         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
666         List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
667 
668         ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
669 
670         PluginEventManager pluginEventManager = mock(PluginEventManager.class);
671 
672         PluginPredicate pluginPredicate = mock(PluginPredicate.class);
673         when(pluginPredicate.matches(earlyPlugin)).thenReturn(false);
674         when(pluginPredicate.matches(laterPlugin)).thenReturn(true);
675 
676         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
677                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
678 
679         defaultPluginManager.earlyStartup();
680         assertThat(defaultPluginManager.getPlugin(earlyKey), is(earlyPlugin));
681         assertThat(defaultPluginManager.getPlugin(laterKey), nullValue());
682 
683         defaultPluginManager.lateStartup();
684         assertThat(defaultPluginManager.getPlugin(earlyKey), is(earlyPlugin));
685         assertThat(defaultPluginManager.getPlugin(laterKey), is(laterPlugin));
686     }
687 
688     public static class ThingsAreWrongListener {
689         private volatile boolean called = false;
690 
691         @PluginEventListener
692         public void onFrameworkShutdown(final PluginFrameworkShutdownEvent event) {
693             called = true;
694             throw new NullPointerException("AAAH!");
695         }
696 
697         public boolean isCalled() {
698             return called;
699         }
700     }
701 
702     @Test
703     public void testShutdownHandlesException() {
704         final ThingsAreWrongListener listener = new ThingsAreWrongListener();
705         pluginEventManager.register(listener);
706 
707         manager = newDefaultPluginManager();
708         manager.init();
709         try {
710             //this should not throw an exception
711             manager.shutdown();
712         } catch (Exception e) {
713             fail("Should not have thrown an exception!");
714         }
715         assertTrue(listener.isCalled());
716     }
717 
718     @Test
719     public void testCannotInitTwice() throws PluginParseException {
720         manager = newDefaultPluginManager();
721         manager.init();
722         try {
723             manager.init();
724             fail("IllegalStateException expected");
725         } catch (final IllegalStateException expected) {
726         }
727     }
728 
729     @Test
730     public void testCannotShutdownTwice() throws PluginParseException {
731         manager = newDefaultPluginManager();
732         manager.init();
733         manager.shutdown();
734         try {
735             manager.shutdown();
736             fail("IllegalStateException expected");
737         } catch (final IllegalStateException expected) {
738         }
739     }
740 
741     private void writeToFile(String file, String line) throws IOException, URISyntaxException {
742         final String resourceName = ClasspathFilePluginMetadata.class.getPackage().getName().replace(".", "/") + "/" + file;
743         URL resource = getClass().getClassLoader().getResource(resourceName);
744 
745         FileOutputStream fout = new FileOutputStream(new File(resource.toURI()), false);
746         fout.write(line.getBytes(), 0, line.length());
747         fout.close();
748 
749     }
750 
751     @Test
752     public void testExceptionOnRequiredPluginNotEnabling() throws Exception {
753         try {
754             writeToFile("application-required-modules.txt", "foo.required:bar");
755             writeToFile("application-required-plugins.txt", "foo.required");
756 
757             final PluginLoader mockPluginLoader = mock(PluginLoader.class);
758             final ModuleDescriptor<Object> badModuleDescriptor = mockFailingModuleDescriptor("foo.required:bar", FAIL_TO_ENABLE);
759 
760             final AbstractModuleDescriptor goodModuleDescriptor = mock(AbstractModuleDescriptor.class);
761             when(goodModuleDescriptor.getKey()).thenReturn("baz");
762             when(goodModuleDescriptor.getCompleteKey()).thenReturn("foo.required:baz");
763 
764             Plugin plugin = mockStaticPlugin("foo.required", goodModuleDescriptor, badModuleDescriptor);
765 
766             when(mockPluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(Collections.singletonList(plugin));
767 
768             manager = newDefaultPluginManager(mockPluginLoader);
769             try {
770                 manager.init();
771             } catch (PluginException e) {
772                 // expected
773                 assertEquals("Unable to validate required plugins or modules", e.getMessage());
774                 return;
775             }
776             fail("A PluginException is expected when trying to initialize the plugins system with required plugins that do not load.");
777         } finally {
778             // remove references from required files
779             writeToFile("application-required-modules.txt", "");
780             writeToFile("application-required-plugins.txt", "");
781         }
782     }
783 
784     @Test
785     public void minimumPluginVersionIsEnforced() throws Exception {
786         // This test tries to install four plugins
787         // - alpha has a minimum version specified which is less than its version, and it should be installed
788         // - beta has a minimum version specified which is equal to its version, and it should be installed
789         // - gamma has a minimum version specified which is greater than its version, and it should not be installed
790         // - delta has no minimum version specified, and it should be installed
791         // - epsilon has a garbage minimum version specified, and it should be installed
792         final String alphaKey = "alpha";
793         final String betaKey = "beta";
794         final String gammaKey = "gamma";
795         final String deltaKey = "delta";
796         final String epsilonKey = "epsilon";
797         final Plugin alphaPlugin = mockPluginWithVersion(alphaKey, "1.2");
798         final Plugin betaPlugin = mockPluginWithVersion(betaKey, "2.3");
799         final Plugin gammaPlugin = mockPluginWithVersion(gammaKey, "3.4");
800         final Plugin deltaPlugin = mockPluginWithVersion(deltaKey, "5.6");
801         final Plugin epsilonPlugin = mockPluginWithVersion(epsilonKey, "7.8");
802 
803         final Plugin[] allPlugins = {alphaPlugin, betaPlugin, gammaPlugin, deltaPlugin, epsilonPlugin};
804 
805         // We need to compareTo to work for plugins that participate in the same addPlugins, so set a good global order
806         mockPluginsSortOrder(allPlugins);
807         final DiscardablePluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
808         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(ImmutableList.copyOf(allPlugins));
809         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
810 
811         manager = new DefaultPluginManager(
812                 mockPluginPersistentStateStore(),
813                 ImmutableList.<PluginLoader>of(pluginLoader),
814                 mock(ModuleDescriptorFactory.class),
815                 pluginEventManager
816         );
817 
818         final File minimumPluginVersionsFile = new File(temporaryDirectory, "mininumPluginVersions.properties");
819         final String badEpsilonVersion = "n0V3r$Ion";
820         final String largeGammaVersion = "4.5";
821         final List<String> minimumPluginVersionsContents = Arrays.asList(
822                 alphaKey + "=0.1", // less than the version of the plugin
823                 betaKey + "=2.3", // equal to the version of the plugin
824                 gammaKey + "=" + largeGammaVersion, // greater than the version of the plugin
825                 // deltaKey is not here, because it has no specified minimum version...
826                 epsilonKey + "=" + badEpsilonVersion // garbage version
827         );
828         writeLines(minimumPluginVersionsFile, minimumPluginVersionsContents);
829         System.setProperty(getMinimumPluginVersionsFileProperty(), minimumPluginVersionsFile.getPath());
830 
831         manager.init();
832         verify(alphaPlugin).install();
833         verify(betaPlugin).install();
834         verify(gammaPlugin, never()).install();
835         // Since gammaPlugin wasn't installed, it should be discarded so the loader can reclaim resources
836         verify(pluginLoader).discardPlugin(gammaPlugin);
837         verify(deltaPlugin).install();
838         verify(epsilonPlugin).install();
839 
840         assertThat(capturedLogging, didLogInfo("Unacceptable plugin", gammaPlugin.toString(), largeGammaVersion));
841         assertThat(capturedLogging, didLogWarn("Cannot compare minimum version", epsilonPlugin.toString(), badEpsilonVersion));
842     }
843 }