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