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