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