View Javadoc

1   package com.atlassian.plugin.loaders;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.net.URISyntaxException;
6   import java.net.URL;
7   import java.util.Arrays;
8   import java.util.Collection;
9   import java.util.List;
10  
11  import com.atlassian.plugin.ModuleDescriptorFactory;
12  import com.atlassian.plugin.Plugin;
13  import com.atlassian.plugin.PluginArtifact;
14  import com.atlassian.plugin.PluginException;
15  import com.atlassian.plugin.event.PluginEventManager;
16  import com.atlassian.plugin.loaders.classloading.DeploymentUnit;
17  import com.atlassian.plugin.loaders.classloading.Scanner;
18  import com.atlassian.plugin.test.PluginJarBuilder;
19  import com.atlassian.plugin.factories.PluginFactory;
20  import com.atlassian.plugin.test.PluginTestUtils;
21  
22  import com.google.common.base.Function;
23  import com.google.common.collect.Iterables;
24  import com.google.common.collect.Lists;
25  
26  import org.apache.commons.io.FileUtils;
27  import org.junit.After;
28  import org.junit.Before;
29  import org.junit.Rule;
30  import org.junit.Test;
31  import org.junit.rules.ExpectedException;
32  import org.mockito.ArgumentCaptor;
33  
34  
35  import static org.hamcrest.Matchers.containsInAnyOrder;
36  import static org.hamcrest.Matchers.instanceOf;
37  import static org.junit.Assert.assertEquals;
38  import static org.junit.Assert.assertThat;
39  import static org.junit.Assert.assertTrue;
40  import static org.mockito.Matchers.isA;
41  import static org.mockito.Matchers.same;
42  import static org.mockito.Mockito.mock;
43  import static org.mockito.Mockito.verify;
44  import static org.mockito.Mockito.when;
45  
46  public class TestBundledPluginLoader
47  {
48      @Rule
49      public ExpectedException expectedException = ExpectedException.none();
50  
51      private File pluginDir;
52  
53      private static final String fooXml = "foo.xml";
54  
55      @Before
56      public void createTemporaryDirectory() throws IOException, URISyntaxException
57      {
58          pluginDir = PluginTestUtils.createTempDirectory(TestBundledPluginLoader.class);
59      }
60  
61      @After
62      public void deleteTemporaryDirectory() throws Exception
63      {
64          FileUtils.deleteDirectory(pluginDir);
65          pluginDir = null;
66      }
67  
68      @Test
69      public void loaderFromZipWithUrlConstructorContainsExpectedFiles() throws IOException
70      {
71          final File bundledZip = buildBundledZip();
72          final BundledPluginLoader loader = buildBundledPluginLoader(bundledZip, true);
73          assertLoaderContains(loader, fooXml);
74      }
75  
76      @Test
77      public void loaderFromZipWithNonUrlConstructorContainsExpectedFiles() throws IOException
78      {
79          final File bundledZip = buildBundledZip();
80          final BundledPluginLoader loader = buildBundledPluginLoader(bundledZip, false);
81          // Yes, this will be empty. For a zip, we must use the constructor that can specify a
82          // directory. However, the code logs and returns and empty loader rather than throwing,
83          // because this is behaviour that will make the jira change most compatible.
84          assertLoaderContains(loader);
85      }
86  
87      @Test
88      public void loaderFromDirectoryWithUrlConstructorContainsExpectedFiles() throws IOException
89      {
90          validateLoaderFromDirectoryContainsExpectedFiles(true);
91      }
92  
93      @Test
94      public void loaderFromDirectoryWithNonUrlConstructorContainsExpectedFiles() throws IOException
95      {
96          validateLoaderFromDirectoryContainsExpectedFiles(false);
97      }
98  
99      private void validateLoaderFromDirectoryContainsExpectedFiles(final boolean useUrlConstructor) throws IOException
100     {
101         final File dir = PluginTestUtils.createTempDirectory(TestBundledPluginLoader.class);
102         FileUtils.writeStringToFile(new File(dir, "foo.txt"), "hello");
103 
104         final BundledPluginLoader loader = buildBundledPluginLoader(dir, useUrlConstructor);
105         assertLoaderContains(loader, "foo.txt");
106     }
107 
108     @Test
109     public void loaderFromFileListWithUrlConstructorContainsExpectedFiles() throws IOException
110     {
111         validateLoaderFromFileListContainsExpectedFiles(true);
112     }
113 
114     @Test
115     public void loaderFromFileListWithNonUrlConstructorContainsExpectedFiles() throws IOException
116     {
117         validateLoaderFromFileListContainsExpectedFiles(false);
118     }
119 
120     private void validateLoaderFromFileListContainsExpectedFiles(final boolean useUrlConstructor) throws IOException
121     {
122         FileUtils.writeStringToFile(new File(pluginDir, "foo.txt"), "hello");
123         FileUtils.writeStringToFile(new File(pluginDir, "bar.txt"), "world");
124         final File listFile = new File(pluginDir, "bundled-plugins" + BundledPluginLoader.getListSuffix());
125         FileUtils.writeStringToFile(listFile, "foo.txt\nbar.txt");
126 
127         final BundledPluginLoader loader = buildBundledPluginLoader(listFile, useUrlConstructor);
128         assertLoaderContains(loader, "foo.txt", "bar.txt");
129     }
130 
131     @Test
132     public void loaderFromUnsupportedFileWithUrlConstructorContainsExpectedFiles() throws IOException
133     {
134         validateLoaderFromUnsupportedFileContainsExpectedFiles(true);
135     }
136 
137     @Test
138     public void loaderFromUnsupportedFileWithNonUrlConstructorContainsExpectedFiles() throws IOException
139     {
140         validateLoaderFromUnsupportedFileContainsExpectedFiles(false);
141     }
142 
143     private void validateLoaderFromUnsupportedFileContainsExpectedFiles(final boolean useUrlConstructor) throws IOException
144     {
145         final File unsupportedFile = new File(pluginDir, "notASuitableFile.unknown-suffix");
146         FileUtils.writeStringToFile(unsupportedFile, "Some\nRandom\nContent\n");
147 
148         final BundledPluginLoader loader = buildBundledPluginLoader(unsupportedFile, useUrlConstructor);
149         assertLoaderContains(loader);
150     }
151 
152     @Test
153     public void loaderScannerDoesNotRemoveUnderlyingFiles() throws IOException
154     {
155         final File bundledZip = buildBundledZip();
156 
157         // Since it's actually a zip, we must use the urlConstructor (hence true)
158         final BundledPluginLoader loader = buildBundledPluginLoader(bundledZip, true);
159         final Scanner scanner = loader.scanner;
160         assertLoaderContains(loader, fooXml);
161         final Collection<DeploymentUnit> units = scanner.getDeploymentUnits();
162         final int countBefore = units.size();
163         int found = 0;
164         for (final DeploymentUnit unit : units)
165         {
166             final File file = unit.getPath();
167             if (fooXml.equals(file.getName()))
168             {
169                 found += 1;
170                 assertTrue(file.exists());
171                 scanner.remove(unit);
172                 // Yes, it should still exist
173                 assertTrue(file.exists());
174             }
175         }
176         // We should have found a foo.txt
177         assertEquals(1, found);
178         // We should not have actually removed it from the scanner
179         assertEquals(countBefore, scanner.getDeploymentUnits().size());
180     }
181 
182     @Test
183     public void loaderRemoveDoesUninstallPlugin() throws IOException
184     {
185         final File bundledZip = buildBundledZip();
186         final Plugin mockPlugin = mock(Plugin.class);
187         final PluginFactory pluginFactory = mock(PluginFactory.class);
188         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
189 
190         final String key = "plugin.key.foo";
191         final ArgumentCaptor<PluginArtifact> pluginArtifact = ArgumentCaptor.forClass(PluginArtifact.class);
192 
193         when(mockPlugin.getKey()).thenReturn(key);
194         when(mockPlugin.isDeleteable()).thenReturn(true);
195         when(mockPlugin.isUninstallable()).thenReturn(true);
196         when(pluginFactory.canCreate(isA(PluginArtifact.class))).thenReturn(key);
197         when(pluginFactory.create(pluginArtifact.capture(), same(moduleDescriptorFactory))).thenReturn(mockPlugin);
198 
199         // Since it's actually a zip, we must use the urlConstructor (hence true)
200         final BundledPluginLoader loader = buildBundledPluginLoader(bundledZip, true, pluginFactory);
201         final Collection<Plugin> plugins = Lists.newArrayList(loader.loadAllPlugins(moduleDescriptorFactory));
202         assertEquals(1, plugins.size());
203         // Note that the plugin we get back is actually wrapped, so we don't test equality
204         final Plugin bundledPlugin = Iterables.getOnlyElement(plugins);
205         assertEquals(key, bundledPlugin.getKey());
206         assertTrue(bundledPlugin.isBundledPlugin());
207         assertTrue(bundledPlugin.isDeleteable());
208         verify(mockPlugin).isDeleteable();
209 
210         // Remove it, and check Plugin was uninstalled
211         loader.removePlugin(bundledPlugin);
212         verify(mockPlugin).isUninstallable();
213         verify(mockPlugin).uninstall();
214         // Check file on disk didn't get removed
215         assertTrue(pluginArtifact.getValue().toFile().exists());
216 
217         // There's no way to query a loader for its plugins, so we try to unload again and look for
218         // the exception. This currently throws because the Plugin is gone. This is a little
219         // brittle, because code evolution in the test subject means it might throw because the
220         // Plugin state changed, but since the Plugin is a mock, it's state doesn't change without
221         // us asking. So it's likely to be good. We could check the exception text, but i reckon
222         // this is even more brittle.
223         expectedException.expect(PluginException.class);
224         loader.removePlugin(bundledPlugin);
225     }
226 
227     @Test
228     public void pluginArtifactsAllowReference() throws IOException
229     {
230         // Use a list file for the bundled plugins to make it easy to get a jar filename in there
231         final File dir = PluginTestUtils.createTempDirectory(TestBundledPluginLoader.class);
232         final File bundleList = new File(dir, "bundled-plugins" + BundledPluginLoader.getListSuffix());
233         FileUtils.writeStringToFile(bundleList, "some.jar\n");
234         final Plugin mockPlugin = mock(Plugin.class);
235         final PluginFactory pluginFactory = mock(PluginFactory.class);
236         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
237 
238         final String key = "plugin.key.foo";
239         final ArgumentCaptor<PluginArtifact> pluginArtifact = ArgumentCaptor.forClass(PluginArtifact.class);
240 
241         when(mockPlugin.getKey()).thenReturn(key);
242         when(pluginFactory.canCreate(isA(PluginArtifact.class))).thenReturn(key);
243         when(pluginFactory.create(pluginArtifact.capture(), same(moduleDescriptorFactory))).thenReturn(mockPlugin);
244 
245         // We could use either constructor, but non url is recommended for non zip source.
246         final BundledPluginLoader loader = buildBundledPluginLoader(bundleList, false, pluginFactory);
247         loader.loadAllPlugins(moduleDescriptorFactory);
248         assertThat(pluginArtifact.getValue(), instanceOf(PluginArtifact.AllowsReference.class));
249         assertTrue(((PluginArtifact.AllowsReference) pluginArtifact.getValue()).allowsReference());
250     }
251 
252     private File buildBundledZip() throws IOException
253     {
254         return new PluginJarBuilder("bundledPlugins")
255             .addResource(fooXml, "<foo/>")
256             .buildWithNoManifest();
257     }
258 
259     private BundledPluginLoader buildBundledPluginLoader(
260             final File bundledPlugins,
261             final boolean useUrlConstructor,
262             final PluginFactory... pluginFactories)
263             throws IOException
264     {
265         final List<PluginFactory> pluginFactoryList = Arrays.asList(pluginFactories);
266         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
267         if (useUrlConstructor)
268         {
269             final URL bundledPluginsUrl = bundledPlugins.toURI().toURL();
270             return new BundledPluginLoader(bundledPluginsUrl, pluginDir, pluginFactoryList, pluginEventManager);
271         }
272         else
273         {
274             return new BundledPluginLoader(bundledPlugins, pluginFactoryList, pluginEventManager);
275         }
276     }
277 
278     private void assertLoaderContains(final BundledPluginLoader loader, final String ... expectedEntries)
279     {
280         final Collection<DeploymentUnit> scanned = loader.scanner.scan();
281         final Iterable<String> actualEntries = Iterables.transform(scanned, new Function<DeploymentUnit, String>()
282         {
283             @Override
284             public String apply(final DeploymentUnit unit)
285             {
286                 return unit.getPath().getName();
287             }
288         });
289         assertThat(actualEntries, containsInAnyOrder(expectedEntries));
290     }
291 }