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
102 private File temporaryDirectory;
103 private File startupOverrideFile;
104
105
106
107
108 private SplitStartupPluginSystemLifecycle manager;
109
110 private PluginEventManager pluginEventManager;
111
112 private ModuleDescriptorFactory descriptorFactory = new DefaultModuleDescriptorFactory(new DefaultHostContainer());
113
114 private DefaultPluginManager newDefaultPluginManager(PluginLoader... pluginLoaders) {
115 DefaultPluginManager dpm = DefaultPluginManager.newBuilder().
116 withPluginLoaders(copyOf(pluginLoaders))
117 .withModuleDescriptorFactory(descriptorFactory)
118 .withPluginEventManager(pluginEventManager)
119 .withStore(new MemoryPluginPersistentStateStore())
120 .withVerifyRequiredPlugins(true)
121 .build();
122 manager = dpm;
123 return dpm;
124 }
125
126 @Before
127 public void setUp() throws Exception {
128 pluginEventManager = new DefaultPluginEventManager();
129 temporaryDirectory = createTempDirectory(TestDefaultPluginManager.class);
130 startupOverrideFile = new File(temporaryDirectory, "startupOverride.properties");
131 }
132
133 @After
134 public void tearDown() throws Exception {
135 deleteQuietly(temporaryDirectory);
136 }
137
138
139 @Test
140 public void delayedPluginsCanBeDisabled() {
141 final String earlyKey = "earlyKey";
142 final String laterKey = "laterKey";
143
144 Wrapper wrapper = new Wrapper(earlyKey, laterKey).invoke(true);
145 DefaultPluginManager defaultPluginManager = wrapper.getDefaultPluginManager();
146 Plugin laterPlugin = wrapper.getLaterPlugin();
147
148 defaultPluginManager.earlyStartup();
149 defaultPluginManager.disablePlugin(laterKey);
150
151 defaultPluginManager.lateStartup();
152 verify(laterPlugin, never()).enable();
153 }
154
155 @Test
156 public void delayedPluginsCanBeEnabled() {
157 final String earlyKey = "earlyKey";
158 final String laterKey = "laterKey";
159
160 Wrapper wrapper = new Wrapper(earlyKey, laterKey).invoke(false);
161 DefaultPluginManager defaultPluginManager = wrapper.getDefaultPluginManager();
162 Plugin laterPlugin = wrapper.getLaterPlugin();
163 defaultPluginManager.earlyStartup();
164 defaultPluginManager.enablePlugin(laterKey);
165
166 defaultPluginManager.lateStartup();
167 verify(laterPlugin).enable();
168 }
169
170 private class Wrapper {
171 private final String earlyKey;
172 private final String laterKey;
173 private Plugin laterPlugin;
174 private DefaultPluginManager defaultPluginManager;
175
176 public Wrapper(String earlyKey, String laterKey) {
177 this.earlyKey = earlyKey;
178 this.laterKey = laterKey;
179 }
180
181 public Plugin getLaterPlugin() {
182 return laterPlugin;
183 }
184
185 public DefaultPluginManager getDefaultPluginManager() {
186 return defaultPluginManager;
187 }
188
189 public Wrapper invoke(final boolean isLatePluginEnabledByDefault) {
190 final PluginPersistentStateStore pluginPersistentStateStore = mock(
191 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
192 when(pluginPersistentStateStore.load().getPluginRestartState(earlyKey)).thenReturn(PluginRestartState.NONE);
193 when(pluginPersistentStateStore.load().getPluginRestartState(laterKey)).thenReturn(PluginRestartState.NONE);
194 doAnswer(new Answer<Void>() {
195 @Override
196 public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
197 final PluginPersistentState pluginState = (PluginPersistentState) invocationOnMock.getArguments()[0];
198 when(pluginPersistentStateStore.load()).thenReturn(pluginState);
199 return null;
200 }
201 }).when(pluginPersistentStateStore).save(isA(PluginPersistentState.class));
202
203 PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
204 Plugin earlyPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
205 laterPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
206 when(earlyPlugin.getKey()).thenReturn(earlyKey);
207 when(laterPlugin.getKey()).thenReturn(laterKey);
208 when(earlyPlugin.isEnabledByDefault()).thenReturn(true);
209 when(laterPlugin.isEnabledByDefault()).thenReturn(isLatePluginEnabledByDefault);
210 List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
211 when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
212 List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
213
214 ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
215
216 PluginEventManager pluginEventManager = mock(PluginEventManager.class);
217
218 PluginPredicate pluginPredicate = mock(PluginPredicate.class);
219 when(pluginPredicate.matches(earlyPlugin)).thenReturn(false);
220 when(pluginPredicate.matches(laterPlugin)).thenReturn(true);
221
222 defaultPluginManager = new DefaultPluginManager(
223 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
224 return this;
225 }
226 }
227
228 @Test
229 public void scanForNewPluginsNotAllowedBeforeLateStartup() {
230 final PluginPersistentStateStore pluginPersistentStateStore = mock(
231 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
232
233 final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
234 when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.<Plugin>asList());
235 final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
236
237 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
238
239 final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
240
241 final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
242 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager);
243
244 defaultPluginManager.earlyStartup();
245
246 expectedException.expect(IllegalStateException.class);
247 defaultPluginManager.scanForNewPlugins();
248 }
249
250 @Test
251 public void scanForNewPluginsDuringLateStartup() {
252 final String pluginKey = "plugin-key";
253 final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
254 when(plugin.getKey()).thenReturn(pluginKey);
255 when(plugin.isEnabledByDefault()).thenReturn(true);
256 when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
257
258 final PluginPersistentStateStore pluginPersistentStateStore = mock(
259 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
260 when(pluginPersistentStateStore.load().isEnabled(plugin)).thenReturn(true);
261
262 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
263
264 final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
265 when(pluginLoader.loadAllPlugins(moduleDescriptorFactory)).thenReturn(Arrays.asList(plugin));
266 when(pluginLoader.loadFoundPlugins(moduleDescriptorFactory)).thenReturn(Arrays.<Plugin>asList());
267 final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
268
269 final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
270
271 final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
272 when(pluginPredicate.matches(plugin)).thenReturn(true);
273
274 final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
275 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
276
277
278
279 final Answer<Object> scanForNewPlugins = new Answer<Object>() {
280 @Override
281 public Object answer(final InvocationOnMock invocation) throws Throwable {
282 defaultPluginManager.scanForNewPlugins();
283 return null;
284 }
285 };
286 doAnswer(scanForNewPlugins).when(pluginEventManager).broadcast(isA(PluginEnabledEvent.class));
287
288 defaultPluginManager.earlyStartup();
289 defaultPluginManager.lateStartup();
290 }
291
292
293 @Test
294 public void earlyStartupDoesNotSavePluginPersistentState() {
295
296 final String pluginKeyPrefix = "pluginWithRestartState_";
297 final ImmutableList.Builder<Plugin> pluginListBuilder = ImmutableList.<Plugin>builder();
298 final PluginPersistentState.Builder pluginPersistentStateBuilder = PluginPersistentState.Builder.create();
299 for (final PluginRestartState pluginRestartState : PluginRestartState.values()) {
300 final String pluginKey = pluginKeyPrefix + pluginRestartState.toString();
301 final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
302 when(plugin.getKey()).thenReturn(pluginKey);
303 pluginListBuilder.add(plugin);
304 pluginPersistentStateBuilder.setPluginRestartState(pluginKey, pluginRestartState);
305
306 }
307 final PluginPersistentStateStore pluginPersistentStateStore = mock(PluginPersistentStateStore.class);
308 when(pluginPersistentStateStore.load()).thenReturn(pluginPersistentStateBuilder.toState());
309 final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
310 when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(pluginListBuilder.build());
311 final List<PluginLoader> pluginLoaders = ImmutableList.of(pluginLoader);
312
313 final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
314 when(pluginPredicate.matches(any(Plugin.class))).thenReturn(false);
315
316 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
317
318 final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
319
320 final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
321 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
322
323 defaultPluginManager.earlyStartup();
324 verify(pluginPersistentStateStore, never()).save(any(PluginPersistentState.class));
325 }
326
327 @Test
328 public void lateStartupRemovesPluginsMarkedForRemoval() {
329 final String earlyKey = "earlyKey";
330 final String laterKey = "laterKey";
331 final Plugin earlyPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
332 final Plugin laterPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
333 when(earlyPlugin.getKey()).thenReturn(earlyKey);
334 when(laterPlugin.getKey()).thenReturn(laterKey);
335
336
337 final PluginPersistentStateStore pluginPersistentStateStore = new MemoryPluginPersistentStateStore();
338 pluginPersistentStateStore.save(PluginPersistentState.Builder.create()
339 .setEnabled(earlyPlugin, !earlyPlugin.isEnabledByDefault())
340 .setEnabled(laterPlugin, !laterPlugin.isEnabledByDefault())
341 .setPluginRestartState(earlyKey, PluginRestartState.REMOVE)
342 .setPluginRestartState(laterKey, PluginRestartState.REMOVE)
343 .toState());
344
345 final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
346 final List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
347 when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
348 final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
349
350 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
351
352 final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
353
354 final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
355 when(pluginPredicate.matches(earlyPlugin)).thenReturn(false);
356 when(pluginPredicate.matches(laterPlugin)).thenReturn(true);
357
358 final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
359 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
360
361 defaultPluginManager.earlyStartup();
362 verify(pluginLoader, never()).removePlugin(any(Plugin.class));
363
364
365 defaultPluginManager.lateStartup();
366 verify(pluginLoader).removePlugin(earlyPlugin);
367 verify(pluginLoader).removePlugin(laterPlugin);
368
369 final PluginPersistentState pluginPersistentState = pluginPersistentStateStore.load();
370
371 assertThat(pluginPersistentState.isEnabled(earlyPlugin), is(earlyPlugin.isEnabledByDefault()));
372 assertThat(pluginPersistentState.isEnabled(laterPlugin), is(laterPlugin.isEnabledByDefault()));
373
374 assertThat(pluginPersistentState.getPluginRestartState(earlyKey), is(PluginRestartState.NONE));
375 assertThat(pluginPersistentState.getPluginRestartState(laterKey), is(PluginRestartState.NONE));
376
377 assertThat(pluginPersistentState.getMap().size(), is(0));
378 }
379
380 @Test
381 public void exampleUsingPersistentStateDelegation() {
382 final String earlyKey = "earlyKey";
383 final String laterKey = "laterKey";
384 final Plugin earlyPlugin = mock(Plugin.class, RETURNS_MOCKS);
385 final Plugin laterPlugin = mock(Plugin.class, RETURNS_MOCKS);
386 when(earlyPlugin.getKey()).thenReturn(earlyKey);
387 when(laterPlugin.getKey()).thenReturn(laterKey);
388 when(earlyPlugin.isEnabledByDefault()).thenReturn(true);
389 when(laterPlugin.isEnabledByDefault()).thenReturn(true);
390
391
392 final boolean tenanted[] = {false};
393 final PluginPersistentStateStore warmStore = new LoadOnlyPluginPersistentStateStore();
394 final PluginPersistentStateStore tenantedStore = new MemoryPluginPersistentStateStore();
395 final PluginPersistentStateStore pluginPersistentStateStore = new DelegatingPluginPersistentStateStore() {
396 @Override
397 public PluginPersistentStateStore getDelegate() {
398 return !tenanted[0] ? warmStore : tenantedStore;
399 }
400 };
401
402 final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
403 final List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
404 when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
405 final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
406
407 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
408
409 final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
410
411 final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
412 when(pluginPredicate.matches(earlyPlugin)).thenReturn(false);
413 when(pluginPredicate.matches(laterPlugin)).thenReturn(true);
414
415 final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
416 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
417
418 defaultPluginManager.earlyStartup();
419
420
421 tenanted[0] = true;
422
423 defaultPluginManager.lateStartup();
424
425 when(earlyPlugin.getPluginState()).thenReturn(PluginState.ENABLED);
426 when(laterPlugin.getPluginState()).thenReturn(PluginState.ENABLED);
427
428
429 defaultPluginManager.disablePlugin(earlyKey);
430 defaultPluginManager.disablePlugin(laterKey);
431
432 final PluginPersistentState pluginPersistentState = tenantedStore.load();
433 assertThat(pluginPersistentState.isEnabled(earlyPlugin), is(false));
434 assertThat(pluginPersistentState.isEnabled(laterPlugin), is(false));
435
436 assertThat(pluginPersistentState.getMap().size(), is(2));
437 }
438
439 @Test
440 public void lateStartupDoesntRetryEnableWhenNotRequested() {
441 validateLateStartupRetryEnable(false);
442 }
443
444 @Test
445 public void lateStartupDoesRetryEnableWhenRequested() {
446 validateLateStartupRetryEnable(true);
447 }
448
449 private void validateLateStartupRetryEnable(final boolean allowEnableRetry) {
450
451 System.setProperty(getLateStartupEnableRetryProperty(), Boolean.toString(allowEnableRetry));
452
453 final Plugin enablesFinePlugin = mockStateChangePlugin("enablesFine", pluginEventManager);
454
455 final Plugin firstEnableFailsPlugin = mockPlugin("firstEnableFails");
456 doAnswerPluginStateChangeWhen(firstEnableFailsPlugin, PluginState.INSTALLED, pluginEventManager).install();
457
458 final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
459
460 mockPluginsSortOrder(enablesFinePlugin, firstEnableFailsPlugin);
461
462 final PluginLoader pluginLoader = mockPluginLoaderForPlugins(enablesFinePlugin, firstEnableFailsPlugin);
463
464 final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
465 pluginPersistentStateStore,
466 ImmutableList.of(pluginLoader),
467 mock(ModuleDescriptorFactory.class),
468 pluginEventManager,
469 mock(PluginExceptionInterception.class)
470 );
471
472 defaultPluginManager.earlyStartup();
473
474 verify(enablesFinePlugin).enable();
475 verify(firstEnableFailsPlugin).enable();
476
477
478 doAnswerPluginStateChangeWhen(firstEnableFailsPlugin, PluginState.ENABLED, pluginEventManager).enable();
479
480 defaultPluginManager.lateStartup();
481
482
483 verify(enablesFinePlugin).enable();
484
485
486
487 if (allowEnableRetry) {
488
489
490 verify(firstEnableFailsPlugin, times(2)).enable();
491 final String pluginString = firstEnableFailsPlugin.toString();
492 assertThat(capturedLogging, didLogWarn("Failed to enable", "fallback", "lateStartup", pluginString));
493 assertThat(capturedLogging, didLogWarn("fallback enabled", "lateStartup", pluginString));
494 } else {
495
496 verify(firstEnableFailsPlugin, times(1)).enable();
497
498
499 assertThat(capturedLogging, not(didLogWarn("fallback", "lateStartup")));
500 }
501 }
502
503 @Test
504 public void startupOverrideFileOverridesPluginInformationAndDelayPredicate() throws Exception {
505 final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
506
507 final String earlyOverrideNoInfoKey = "earlyOverrideNoInfoKey";
508 final String lateOverrideNoInfoKey = "lateOverrideNoInfoKey";
509 final Plugin earlyOverrideNoInfoPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
510 final Plugin lateOverrideNoInfoPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
511 when(earlyOverrideNoInfoPlugin.getKey()).thenReturn(earlyOverrideNoInfoKey);
512 when(lateOverrideNoInfoPlugin.getKey()).thenReturn(lateOverrideNoInfoKey);
513
514 final String earlyOverrideLateInfoKey = "earlyOverrideLateInfoKey";
515 final String lateOverrideEarlyInfoKey = "lateOverrideEarlyInfoKey";
516 final Plugin earlyOverrideLateInfoPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
517 final Plugin lateOverrideEarlyInfoPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
518 when(earlyOverrideLateInfoPlugin.getKey()).thenReturn(earlyOverrideLateInfoKey);
519 when(earlyOverrideLateInfoPlugin.getPluginInformation().getStartup()).thenReturn("late");
520 when(lateOverrideEarlyInfoPlugin.getKey()).thenReturn(lateOverrideEarlyInfoKey);
521 when(lateOverrideEarlyInfoPlugin.getPluginInformation().getStartup()).thenReturn("early");
522
523 final List<String> overrideContents = Arrays.asList(
524 earlyOverrideNoInfoKey + "=early",
525 lateOverrideNoInfoKey + "=late",
526 earlyOverrideLateInfoKey + "=early",
527 lateOverrideEarlyInfoKey + "=late"
528 );
529 writeLines(startupOverrideFile, overrideContents);
530 System.setProperty(getStartupOverrideFileProperty(), startupOverrideFile.getPath());
531
532
533 mockPluginsSortOrder(
534 earlyOverrideLateInfoPlugin, earlyOverrideNoInfoPlugin, lateOverrideEarlyInfoPlugin, lateOverrideNoInfoPlugin);
535
536 final List<Plugin> allPlugins = Arrays.asList(
537 earlyOverrideNoInfoPlugin, lateOverrideNoInfoPlugin, earlyOverrideLateInfoPlugin, lateOverrideEarlyInfoPlugin);
538
539 final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
540 when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(allPlugins);
541 final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
542
543 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
544
545 final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
546
547 final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
548
549
550
551 when(pluginPredicate.matches(earlyOverrideNoInfoPlugin)).thenReturn(true);
552 when(pluginPredicate.matches(lateOverrideNoInfoPlugin)).thenReturn(false);
553 when(pluginPredicate.matches(earlyOverrideLateInfoPlugin)).thenReturn(false);
554 when(pluginPredicate.matches(lateOverrideEarlyInfoPlugin)).thenReturn(true);
555
556 final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
557 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
558
559 defaultPluginManager.earlyStartup();
560 verify(earlyOverrideNoInfoPlugin).install();
561 verify(earlyOverrideLateInfoPlugin).install();
562 verify(lateOverrideNoInfoPlugin, never()).install();
563 verify(lateOverrideEarlyInfoPlugin, never()).install();
564
565 defaultPluginManager.lateStartup();
566 verify(lateOverrideNoInfoPlugin).install();
567 verify(lateOverrideEarlyInfoPlugin).install();
568 }
569
570 @Test
571 public void earlyLateStartupEvents() {
572 final PluginPersistentStateStore pluginPersistentStateStore = mock(
573 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
574
575 final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
576 when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.<Plugin>asList());
577 final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
578
579 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
580
581 final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
582
583 final SplitStartupPluginSystemLifecycle splitStartupPluginSystemLifecycle = new DefaultPluginManager(
584 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager);
585
586 splitStartupPluginSystemLifecycle.earlyStartup();
587 final ArgumentCaptor<Object> earlyEvents = ArgumentCaptor.forClass(Object.class);
588 verify(pluginEventManager, times(2)).broadcast(earlyEvents.capture());
589 assertThat(earlyEvents.getAllValues(), contains(
590 instanceOf(PluginFrameworkStartingEvent.class), instanceOf(PluginFrameworkDelayedEvent.class)));
591
592
593 reset(pluginEventManager);
594
595 splitStartupPluginSystemLifecycle.lateStartup();
596 final ArgumentCaptor<Object> laterEvents = ArgumentCaptor.forClass(Object.class);
597 verify(pluginEventManager, times(2)).broadcast(laterEvents.capture());
598 assertThat(laterEvents.getAllValues(), contains(
599 instanceOf(PluginFrameworkResumingEvent.class), instanceOf(PluginFrameworkStartedEvent.class)));
600 }
601
602 @Test
603 public void startupElementInPluginInformationOverridesDelayPredicate() {
604 final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
605
606 final String earlyKey = "earlyKey";
607 final String laterKey = "laterKey";
608 final Plugin earlyPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
609 final Plugin laterPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
610 when(earlyPlugin.getKey()).thenReturn(earlyKey);
611 when(earlyPlugin.getPluginInformation().getStartup()).thenReturn("early");
612 when(laterPlugin.getKey()).thenReturn(laterKey);
613 when(laterPlugin.getPluginInformation().getStartup()).thenReturn("late");
614
615
616
617
618 when(earlyPlugin.compareTo(laterPlugin)).thenReturn(-1);
619 when(laterPlugin.compareTo(earlyPlugin)).thenReturn(1);
620
621 final PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
622 final List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
623 when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
624 final List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
625
626 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
627
628 final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
629
630 final PluginPredicate pluginPredicate = mock(PluginPredicate.class);
631
632 when(pluginPredicate.matches(earlyPlugin)).thenReturn(true);
633 when(pluginPredicate.matches(laterPlugin)).thenReturn(false);
634
635 final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
636 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
637
638 defaultPluginManager.earlyStartup();
639 verify(earlyPlugin).install();
640 verify(laterPlugin, never()).install();
641
642 defaultPluginManager.lateStartup();
643 verify(laterPlugin).install();
644 }
645
646 @Test
647 public void delayedPluginsAreDelayed() {
648 final String earlyKey = "earlyKey";
649 final String laterKey = "laterKey";
650
651 final PluginPersistentStateStore pluginPersistentStateStore = mock(
652 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
653 when(pluginPersistentStateStore.load().getPluginRestartState(earlyKey)).thenReturn(PluginRestartState.NONE);
654 when(pluginPersistentStateStore.load().getPluginRestartState(laterKey)).thenReturn(PluginRestartState.NONE);
655
656 PluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
657 Plugin earlyPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
658 Plugin laterPlugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
659 when(earlyPlugin.getKey()).thenReturn(earlyKey);
660 when(laterPlugin.getKey()).thenReturn(laterKey);
661 List<Plugin> bothPlugins = Arrays.asList(earlyPlugin, laterPlugin);
662 when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(bothPlugins);
663 List<PluginLoader> pluginLoaders = Arrays.asList(pluginLoader);
664
665 ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
666
667 PluginEventManager pluginEventManager = mock(PluginEventManager.class);
668
669 PluginPredicate pluginPredicate = mock(PluginPredicate.class);
670 when(pluginPredicate.matches(earlyPlugin)).thenReturn(false);
671 when(pluginPredicate.matches(laterPlugin)).thenReturn(true);
672
673 final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
674 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager, pluginPredicate);
675
676 defaultPluginManager.earlyStartup();
677 assertThat(defaultPluginManager.getPlugin(earlyKey), is(earlyPlugin));
678 assertThat(defaultPluginManager.getPlugin(laterKey), nullValue());
679
680 defaultPluginManager.lateStartup();
681 assertThat(defaultPluginManager.getPlugin(earlyKey), is(earlyPlugin));
682 assertThat(defaultPluginManager.getPlugin(laterKey), is(laterPlugin));
683 }
684
685 public static class ThingsAreWrongListener {
686 private volatile boolean called = false;
687
688 @PluginEventListener
689 public void onFrameworkShutdown(final PluginFrameworkShutdownEvent event) {
690 called = true;
691 throw new NullPointerException("AAAH!");
692 }
693
694 public boolean isCalled() {
695 return called;
696 }
697 }
698
699 @Test
700 public void testShutdownHandlesException() {
701 final ThingsAreWrongListener listener = new ThingsAreWrongListener();
702 pluginEventManager.register(listener);
703
704 manager = newDefaultPluginManager();
705 manager.init();
706 try {
707
708 manager.shutdown();
709 } catch (Exception e) {
710 fail("Should not have thrown an exception!");
711 }
712 assertTrue(listener.isCalled());
713 }
714
715 @Test
716 public void testCannotInitTwice() throws PluginParseException {
717 manager = newDefaultPluginManager();
718 manager.init();
719 try {
720 manager.init();
721 fail("IllegalStateException expected");
722 } catch (final IllegalStateException expected) {
723 }
724 }
725
726 @Test
727 public void testCannotShutdownTwice() throws PluginParseException {
728 manager = newDefaultPluginManager();
729 manager.init();
730 manager.shutdown();
731 try {
732 manager.shutdown();
733 fail("IllegalStateException expected");
734 } catch (final IllegalStateException expected) {
735 }
736 }
737
738 private void writeToFile(String file, String line) throws IOException, URISyntaxException {
739 final String resourceName = ClasspathFilePluginMetadata.class.getPackage().getName().replace(".", "/") + "/" + file;
740 URL resource = getClass().getClassLoader().getResource(resourceName);
741
742 FileOutputStream fout = new FileOutputStream(new File(resource.toURI()), false);
743 fout.write(line.getBytes(), 0, line.length());
744 fout.close();
745
746 }
747
748 @Test
749 public void testExceptionOnRequiredPluginNotEnabling() throws Exception {
750 try {
751 writeToFile("application-required-modules.txt", "foo.required:bar");
752 writeToFile("application-required-plugins.txt", "foo.required");
753
754 final PluginLoader mockPluginLoader = mock(PluginLoader.class);
755 final ModuleDescriptor<Object> badModuleDescriptor = mockFailingModuleDescriptor("foo.required:bar", FAIL_TO_ENABLE);
756
757 final AbstractModuleDescriptor goodModuleDescriptor = mock(AbstractModuleDescriptor.class);
758 when(goodModuleDescriptor.getKey()).thenReturn("baz");
759 when(goodModuleDescriptor.getCompleteKey()).thenReturn("foo.required:baz");
760
761 Plugin plugin = mockStaticPlugin("foo.required", goodModuleDescriptor, badModuleDescriptor);
762
763 when(mockPluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(Collections.singletonList(plugin));
764
765 manager = newDefaultPluginManager(mockPluginLoader);
766 try {
767 manager.init();
768 } catch (PluginException e) {
769
770 assertEquals("Unable to validate required plugins or modules", e.getMessage());
771 return;
772 }
773 fail("A PluginException is expected when trying to initialize the plugins system with required plugins that do not load.");
774 } finally {
775
776 writeToFile("application-required-modules.txt", "");
777 writeToFile("application-required-plugins.txt", "");
778 }
779 }
780
781 @Test
782 public void minimumPluginVersionIsEnforced() throws Exception {
783
784
785
786
787
788
789 final String alphaKey = "alpha";
790 final String betaKey = "beta";
791 final String gammaKey = "gamma";
792 final String deltaKey = "delta";
793 final String epsilonKey = "epsilon";
794 final Plugin alphaPlugin = mockPluginWithVersion(alphaKey, "1.2");
795 final Plugin betaPlugin = mockPluginWithVersion(betaKey, "2.3");
796 final Plugin gammaPlugin = mockPluginWithVersion(gammaKey, "3.4");
797 final Plugin deltaPlugin = mockPluginWithVersion(deltaKey, "5.6");
798 final Plugin epsilonPlugin = mockPluginWithVersion(epsilonKey, "7.8");
799
800 final Plugin[] allPlugins = {alphaPlugin, betaPlugin, gammaPlugin, deltaPlugin, epsilonPlugin};
801
802
803 mockPluginsSortOrder(allPlugins);
804 final DiscardablePluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
805 when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(ImmutableList.copyOf(allPlugins));
806 final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
807
808 manager = new DefaultPluginManager(
809 mockPluginPersistentStateStore(),
810 ImmutableList.<PluginLoader>of(pluginLoader),
811 mock(ModuleDescriptorFactory.class),
812 pluginEventManager
813 );
814
815 final File minimumPluginVersionsFile = new File(temporaryDirectory, "mininumPluginVersions.properties");
816 final String badEpsilonVersion = "n0V3r$Ion";
817 final String largeGammaVersion = "4.5";
818 final List<String> minimumPluginVersionsContents = Arrays.asList(
819 alphaKey + "=0.1",
820 betaKey + "=2.3",
821 gammaKey + "=" + largeGammaVersion,
822
823 epsilonKey + "=" + badEpsilonVersion
824 );
825 writeLines(minimumPluginVersionsFile, minimumPluginVersionsContents);
826 System.setProperty(getMinimumPluginVersionsFileProperty(), minimumPluginVersionsFile.getPath());
827
828 manager.init();
829 verify(alphaPlugin).install();
830 verify(betaPlugin).install();
831 verify(gammaPlugin, never()).install();
832
833 verify(pluginLoader).discardPlugin(gammaPlugin);
834 verify(deltaPlugin).install();
835 verify(epsilonPlugin).install();
836
837 assertThat(capturedLogging, didLogInfo("Unacceptable plugin", gammaPlugin.toString(), largeGammaVersion));
838 assertThat(capturedLogging, didLogWarn("Cannot compare minimum version", epsilonPlugin.toString(), badEpsilonVersion));
839 }
840 }