View Javadoc
1   package com.atlassian.plugin.loaders;
2   
3   import com.atlassian.annotations.Internal;
4   import com.atlassian.plugin.InstallationMode;
5   import com.atlassian.plugin.ModuleDescriptor;
6   import com.atlassian.plugin.ModuleDescriptorFactory;
7   import com.atlassian.plugin.Plugin;
8   import com.atlassian.plugin.PluginArtifact;
9   import com.atlassian.plugin.PluginArtifactFactory;
10  import com.atlassian.plugin.PluginDependencies;
11  import com.atlassian.plugin.PluginException;
12  import com.atlassian.plugin.PluginInformation;
13  import com.atlassian.plugin.PluginState;
14  import com.atlassian.plugin.Resourced;
15  import com.atlassian.plugin.elements.ResourceDescriptor;
16  import com.atlassian.plugin.elements.ResourceLocation;
17  import com.atlassian.plugin.event.PluginEventManager;
18  import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
19  import com.atlassian.plugin.factories.PluginFactory;
20  import com.atlassian.plugin.impl.UnloadablePlugin;
21  import com.atlassian.plugin.loaders.classloading.DeploymentUnit;
22  import com.atlassian.plugin.loaders.classloading.Scanner;
23  import com.google.common.collect.Iterables;
24  import org.dom4j.Element;
25  import org.junit.Before;
26  import org.junit.Rule;
27  import org.junit.Test;
28  import org.junit.rules.ExpectedException;
29  import org.junit.runner.RunWith;
30  import org.mockito.Mock;
31  import org.mockito.junit.MockitoJUnitRunner;
32  
33  import javax.annotation.Nonnull;
34  import javax.annotation.Nullable;
35  import java.io.File;
36  import java.io.InputStream;
37  import java.net.URL;
38  import java.util.Arrays;
39  import java.util.Collection;
40  import java.util.Collections;
41  import java.util.Date;
42  import java.util.List;
43  import java.util.Optional;
44  import java.util.Set;
45  
46  import static org.hamcrest.MatcherAssert.assertThat;
47  import static org.hamcrest.Matchers.containsInAnyOrder;
48  import static org.hamcrest.Matchers.is;
49  import static org.hamcrest.Matchers.nullValue;
50  import static org.junit.Assert.assertEquals;
51  import static org.junit.Assert.assertNotNull;
52  import static org.junit.Assert.assertNotSame;
53  import static org.junit.Assert.assertSame;
54  import static org.junit.Assert.assertTrue;
55  import static org.junit.Assert.fail;
56  import static org.mockito.ArgumentMatchers.any;
57  import static org.mockito.Mockito.mock;
58  import static org.mockito.Mockito.never;
59  import static org.mockito.Mockito.verify;
60  import static org.mockito.Mockito.when;
61  
62  @RunWith(MockitoJUnitRunner.Silent.class)
63  public class TestScanningPluginLoader {
64      private static final String PLUGIN_KEY = "plugin-key";
65  
66      @Rule
67      public final ExpectedException expectedException = ExpectedException.none();
68  
69      @Mock
70      private PluginArtifactFactory pluginArtifactFactory;
71  
72      @Mock
73      private PluginArtifact pluginArtifact;
74  
75      @Mock
76      private PluginFactory pluginFactory;
77  
78      @Mock
79      private ModuleDescriptorFactory moduleDescriptorFactory;
80  
81      @Mock
82      private Plugin plugin;
83  
84      @Mock
85      private Scanner scanner;
86  
87      @Mock
88      private PluginEventManager pluginEventManager;
89  
90      @Mock
91      private Element module;
92  
93      private DeploymentUnit deploymentUnit;
94  
95      @Before
96      public void configureMocks() {
97          deploymentUnit = new DeploymentUnit(new File("foo.jar"));
98          when(plugin.getKey()).thenReturn(PLUGIN_KEY);
99          when(pluginArtifactFactory.create(deploymentUnit.getPath().toURI())).thenReturn(pluginArtifact);
100         when(pluginFactory.canCreate(pluginArtifact)).thenReturn("foo");
101         when(scanner.getDeploymentUnits()).thenReturn(Arrays.asList(deploymentUnit));
102     }
103 
104     @Test
105     public void loadAllPluginsLoadsPlugin() {
106         when(pluginFactory.create(pluginArtifact, moduleDescriptorFactory)).thenReturn(plugin);
107 
108         final ScanningPluginLoader loader = buildScanningPluginLoader();
109         final Iterable<Plugin> plugins = loader.loadAllPlugins(moduleDescriptorFactory);
110         assertThat(plugins, containsInAnyOrder(plugin));
111     }
112 
113     @Test
114     public void removeEnabledPluginFails() {
115         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
116 
117         final ScanningPluginLoader loader = buildScanningPluginLoader();
118 
119         expectedException.expect(PluginException.class);
120         expectedException.expectMessage(PLUGIN_KEY);
121         loader.removePlugin(plugin);
122     }
123 
124     @Test
125     public void removeNotUninstallablePluginFails() {
126         when(plugin.isUninstallable()).thenReturn(false);
127 
128         final ScanningPluginLoader loader = buildScanningPluginLoader();
129 
130         expectedException.expect(PluginException.class);
131         expectedException.expectMessage(PLUGIN_KEY);
132         loader.removePlugin(plugin);
133     }
134 
135     @Test
136     public void removeDeleteablePluginDoesUninstallAndDelete() {
137         when(plugin.isUninstallable()).thenReturn(true);
138         when(plugin.isDeleteable()).thenReturn(true);
139         when(pluginFactory.create(pluginArtifact, moduleDescriptorFactory)).thenReturn(plugin);
140 
141         final ScanningPluginLoader loader = buildScanningPluginLoader();
142         final Iterable<Plugin> plugins = loader.loadAllPlugins(moduleDescriptorFactory);
143         assertThat(plugins, containsInAnyOrder(plugin));
144         loader.removePlugin(plugin);
145         verify(plugin).uninstall();
146         verify(scanner).remove(deploymentUnit);
147     }
148 
149     @Test
150     public void removeNotDeleteablePluginDoesUninstallButDoesntDelete() {
151         when(plugin.isUninstallable()).thenReturn(true);
152         when(plugin.isDeleteable()).thenReturn(false);
153         when(pluginFactory.create(pluginArtifact, moduleDescriptorFactory)).thenReturn(plugin);
154 
155         final ScanningPluginLoader loader = buildScanningPluginLoader();
156         final Iterable<Plugin> plugins = loader.loadAllPlugins(moduleDescriptorFactory);
157         assertThat(plugins, containsInAnyOrder(plugin));
158         loader.removePlugin(plugin);
159         verify(plugin).uninstall();
160         verify(scanner, never()).remove(any(DeploymentUnit.class));
161     }
162 
163     @Test
164     public void discardedPluginIsNotTracked() {
165         when(plugin.isUninstallable()).thenReturn(true);
166         when(pluginFactory.create(pluginArtifact, moduleDescriptorFactory)).thenReturn(plugin);
167 
168         final ScanningPluginLoader loader = buildScanningPluginLoader();
169         final Iterable<Plugin> plugins = loader.loadAllPlugins(moduleDescriptorFactory);
170         assertThat(plugins, containsInAnyOrder(plugin));
171         loader.discardPlugin(plugin);
172         try {
173             // Discarded, so removal should fail even though isUninstallable
174             loader.removePlugin(plugin);
175             fail();
176         } catch (final PluginException pe) {
177             // Expected
178         }
179         // Shutdown should not result in uninstall of discarded plugin
180         loader.onShutdown(mock(PluginFrameworkShutdownEvent.class));
181         verify(plugin, never()).uninstall();
182     }
183 
184     @Test
185     public void shutdownUninstallsUninstallablePlugin() {
186         when(plugin.isUninstallable()).thenReturn(true);
187         when(pluginFactory.create(pluginArtifact, moduleDescriptorFactory)).thenReturn(plugin);
188 
189         final ScanningPluginLoader loader = buildScanningPluginLoader();
190         loader.loadAllPlugins(moduleDescriptorFactory);
191         loader.onShutdown(mock(PluginFrameworkShutdownEvent.class));
192         verify(plugin).uninstall();
193     }
194 
195     @Test
196     public void shutdownDoesNotUninstallNotUninstallablePlugin() {
197         when(plugin.isUninstallable()).thenReturn(false);
198         when(pluginFactory.create(pluginArtifact, moduleDescriptorFactory)).thenReturn(plugin);
199 
200         final ScanningPluginLoader loader = buildScanningPluginLoader();
201         loader.loadAllPlugins(moduleDescriptorFactory);
202         loader.onShutdown(mock(PluginFrameworkShutdownEvent.class));
203         verify(plugin, never()).uninstall();
204     }
205 
206     @Test
207     public void factoryThrowingRuntimeExceptionYieldsUnloadablePlugin() {
208         factoryThrowingYieldsUnloadablePlugin(new IllegalArgumentException());
209     }
210 
211     @Test
212     public void factoryThrowingErrorYieldsUnloadablePlugin() {
213         factoryThrowingYieldsUnloadablePlugin(new NoClassDefFoundError());
214     }
215 
216     private void factoryThrowingYieldsUnloadablePlugin(final Throwable throwable) {
217         when(pluginFactory.create(pluginArtifact, moduleDescriptorFactory)).thenThrow(throwable);
218 
219         final ScanningPluginLoader loader = buildScanningPluginLoader();
220         final Iterable<Plugin> plugins = loader.loadAllPlugins(moduleDescriptorFactory);
221         assertNotNull(plugins);
222         assertEquals(1, Iterables.size(plugins));
223         assertTrue(Iterables.getOnlyElement(plugins) instanceof UnloadablePlugin);
224     }
225 
226     @Test
227     public void pluginLoaderCallsPostProcess() {
228         when(plugin.isUninstallable()).thenReturn(true);
229         when(pluginFactory.create(pluginArtifact, moduleDescriptorFactory)).thenReturn(plugin);
230 
231         final ScanningPluginLoader loader = new ScanningPluginLoader(
232                 scanner, Collections.singletonList(pluginFactory), pluginArtifactFactory, pluginEventManager) {
233             @Override
234             protected Plugin postProcess(final Plugin plugin) {
235                 return new WrappedPlugin(plugin);
236             }
237         };
238         final Iterable<Plugin> allPlugins = loader.loadAllPlugins(moduleDescriptorFactory);
239         assertPluginsIsWrapperFor(allPlugins, plugin);
240 
241         final DeploymentUnit unitB = new DeploymentUnit(new File("bar.jar"));
242         final PluginArtifact pluginArtifactB = mock(PluginArtifact.class);
243         final Plugin pluginB = mock(Plugin.class);
244         when(scanner.scan()).thenReturn(Arrays.asList(unitB));
245         when(pluginArtifactFactory.create(unitB.getPath().toURI())).thenReturn(pluginArtifactB);
246         when(pluginFactory.canCreate(pluginArtifactB)).thenReturn("bar");
247         when(pluginFactory.create(pluginArtifactB, moduleDescriptorFactory)).thenReturn(pluginB);
248 
249         final Iterable<Plugin> foundPlugins = loader.loadFoundPlugins(moduleDescriptorFactory);
250         assertPluginsIsWrapperFor(foundPlugins, pluginB);
251     }
252 
253     @Test
254     public void createModule() {
255         final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class);
256 
257         final ScanningPluginLoader scanningPluginLoader = buildScanningPluginLoader();
258 
259         when(pluginFactory.createModule(plugin, module, moduleDescriptorFactory)).thenReturn(moduleDescriptor);
260 
261         assertThat(scanningPluginLoader.createModule(plugin, module, moduleDescriptorFactory), is(moduleDescriptor));
262     }
263 
264     @Test
265     public void createModuleNoFactory() {
266         final ScanningPluginLoader scanningPluginLoader = buildScanningPluginLoader();
267 
268         when(pluginFactory.createModule(plugin, module, moduleDescriptorFactory)).thenReturn(null);
269 
270         assertThat(scanningPluginLoader.createModule(plugin, module, moduleDescriptorFactory), nullValue());
271     }
272 
273     /**
274      * A wrapper class for the postProcess test.
275      *
276      * By using a wrapper here, we guarantee postProcess is called, because no one else
277      * could have an instance of this private class.
278      */
279     private static class WrappedPlugin implements Plugin {
280 
281         private final Plugin delegate;
282 
283         public WrappedPlugin(final Plugin plugin) {
284             delegate = plugin;
285         }
286 
287         @Override
288         public int getPluginsVersion() {
289             return delegate.getPluginsVersion();
290         }
291 
292         @Override
293         public void setPluginsVersion(int version) {
294             delegate.setPluginsVersion(version);
295         }
296 
297         @Override
298         public String getName() {
299             return delegate.getName();
300         }
301 
302         @Override
303         public void setName(String name) {
304             delegate.setName(name);
305         }
306 
307         @Override
308         public String getI18nNameKey() {
309             return delegate.getI18nNameKey();
310         }
311 
312         @Override
313         public void setI18nNameKey(String i18nNameKey) {
314             delegate.setI18nNameKey(i18nNameKey);
315         }
316 
317         @Override
318         public String getKey() {
319             return delegate.getKey();
320         }
321 
322         @Override
323         public void setKey(String aPackage) {
324             delegate.setKey(aPackage);
325         }
326 
327         @Override
328         public void addModuleDescriptor(ModuleDescriptor<?> moduleDescriptor) {
329             delegate.addModuleDescriptor(moduleDescriptor);
330         }
331 
332         @Override
333         public Collection<ModuleDescriptor<?>> getModuleDescriptors() {
334             return delegate.getModuleDescriptors();
335         }
336 
337         @Override
338         public ModuleDescriptor<?> getModuleDescriptor(String key) {
339             return delegate.getModuleDescriptor(key);
340         }
341 
342         @Override
343         public <M> List<ModuleDescriptor<M>> getModuleDescriptorsByModuleClass(Class<M> moduleClass) {
344             return delegate.getModuleDescriptorsByModuleClass(moduleClass);
345         }
346 
347         @Override
348         public InstallationMode getInstallationMode() {
349             return delegate.getInstallationMode();
350         }
351 
352         @Override
353         public boolean isEnabledByDefault() {
354             return delegate.isEnabledByDefault();
355         }
356 
357         @Override
358         public void setEnabledByDefault(boolean enabledByDefault) {
359             delegate.setEnabledByDefault(enabledByDefault);
360         }
361 
362         @Override
363         public PluginInformation getPluginInformation() {
364             return delegate.getPluginInformation();
365         }
366 
367         @Override
368         public void setPluginInformation(PluginInformation pluginInformation) {
369             delegate.setPluginInformation(pluginInformation);
370         }
371 
372         @Override
373         public void setResources(Resourced resources) {
374             delegate.setResources(resources);
375         }
376 
377         @Override
378         public PluginState getPluginState() {
379             return delegate.getPluginState();
380         }
381 
382         @Override
383         public boolean isSystemPlugin() {
384             return delegate.isSystemPlugin();
385         }
386 
387         @Override
388         public void setSystemPlugin(boolean system) {
389             delegate.setSystemPlugin(system);
390         }
391 
392         @Override
393         public boolean containsSystemModule() {
394             return delegate.containsSystemModule();
395         }
396 
397         @Override
398         public boolean isBundledPlugin() {
399             return delegate.isBundledPlugin();
400         }
401 
402         @Override
403         public Date getDateLoaded() {
404             return delegate.getDateLoaded();
405         }
406 
407         @Override
408         public Date getDateInstalled() {
409             return delegate.getDateInstalled();
410         }
411 
412         @Override
413         public boolean isUninstallable() {
414             return delegate.isUninstallable();
415         }
416 
417         @Override
418         public boolean isDeleteable() {
419             return delegate.isDeleteable();
420         }
421 
422         @Override
423         public boolean isDynamicallyLoaded() {
424             return delegate.isDynamicallyLoaded();
425         }
426 
427         @Override
428         public <T> Class<T> loadClass(String clazz, Class<?> callingClass) throws ClassNotFoundException {
429             return delegate.loadClass(clazz, callingClass);
430         }
431 
432         @Override
433         public ClassLoader getClassLoader() {
434             return delegate.getClassLoader();
435         }
436 
437         @Override
438         public URL getResource(String path) {
439             return delegate.getResource(path);
440         }
441 
442         @Override
443         public InputStream getResourceAsStream(String name) {
444             return delegate.getResourceAsStream(name);
445         }
446 
447         @Override
448         public void install() throws PluginException {
449             delegate.install();
450         }
451 
452         @Override
453         public void uninstall() throws PluginException {
454             delegate.uninstall();
455         }
456 
457         @Override
458         public void enable() throws PluginException {
459             delegate.enable();
460         }
461 
462         @Override
463         public void disable() throws PluginException {
464             delegate.disable();
465         }
466 
467         @Override
468         @Nonnull
469         public PluginDependencies getDependencies() {
470             return delegate.getDependencies();
471         }
472 
473         @Override
474         public Set<String> getActivePermissions() {
475             return delegate.getActivePermissions();
476         }
477 
478         @Override
479         public boolean hasAllPermissions() {
480             return delegate.hasAllPermissions();
481         }
482 
483         @Override
484         public void resolve() {
485             delegate.resolve();
486         }
487 
488         @Override
489         @Nullable
490         public Date getDateEnabling() {
491             return delegate.getDateEnabling();
492         }
493 
494         @Override
495         @Nullable
496         public Date getDateEnabled() {
497             return delegate.getDateEnabled();
498         }
499 
500         @Override
501         @Internal
502         public PluginArtifact getPluginArtifact() {
503             return delegate.getPluginArtifact();
504         }
505 
506         @Override
507         public Optional<String> getScopeKey() {
508             return delegate.getScopeKey();
509         }
510 
511         @Override
512         public List<ResourceDescriptor> getResourceDescriptors() {
513             return delegate.getResourceDescriptors();
514         }
515 
516         @Override
517         public ResourceDescriptor getResourceDescriptor(String type, String name) {
518             return delegate.getResourceDescriptor(type, name);
519         }
520 
521         @Override
522         public ResourceLocation getResourceLocation(String type, String name) {
523             return delegate.getResourceLocation(type, name);
524         }
525 
526         @Override
527         public int compareTo(Plugin o) {
528             return delegate.compareTo(o);
529         }
530     }
531 
532     private void assertPluginsIsWrapperFor(final Iterable<Plugin> plugins, final Plugin originalPlugin) {
533         assertNotNull(plugins);
534         assertEquals(1, Iterables.size(plugins));
535         final Plugin loadedPlugin = Iterables.getOnlyElement(plugins);
536         assertNotSame(loadedPlugin, originalPlugin);
537         assertTrue(loadedPlugin instanceof WrappedPlugin);
538         final WrappedPlugin wrappedPlugin = (WrappedPlugin) loadedPlugin;
539         assertSame(wrappedPlugin.delegate, originalPlugin);
540     }
541 
542     private ScanningPluginLoader buildScanningPluginLoader() {
543         return new ScanningPluginLoader(scanner, Collections.singletonList(pluginFactory), pluginArtifactFactory, pluginEventManager);
544     }
545 }