1 package com.atlassian.plugin.loaders;
2
3 import com.atlassian.plugin.ModuleDescriptorFactory;
4 import com.atlassian.plugin.Plugin;
5 import com.atlassian.plugin.PluginArtifact;
6 import com.atlassian.plugin.PluginException;
7 import com.atlassian.plugin.PluginInternal;
8 import com.atlassian.plugin.ReferenceMode;
9 import com.atlassian.plugin.event.PluginEventManager;
10 import com.atlassian.plugin.factories.PluginFactory;
11 import com.atlassian.plugin.loaders.classloading.DeploymentUnit;
12 import com.atlassian.plugin.loaders.classloading.Scanner;
13 import com.atlassian.plugin.test.CapturedLogging;
14 import com.atlassian.plugin.test.PluginJarBuilder;
15 import com.atlassian.plugin.test.PluginTestUtils;
16 import com.google.common.base.Function;
17 import com.google.common.collect.Iterables;
18 import com.google.common.collect.Lists;
19 import org.apache.commons.io.FileUtils;
20 import org.junit.After;
21 import org.junit.Before;
22 import org.junit.Rule;
23 import org.junit.Test;
24 import org.junit.rules.ExpectedException;
25 import org.mockito.ArgumentCaptor;
26
27 import java.io.File;
28 import java.io.IOException;
29 import java.net.URL;
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.List;
33
34 import static com.atlassian.plugin.test.CapturedLogging.didLogWarn;
35 import static org.hamcrest.Matchers.containsInAnyOrder;
36 import static org.hamcrest.Matchers.instanceOf;
37 import static org.hamcrest.Matchers.is;
38 import static org.junit.Assert.assertEquals;
39 import static org.junit.Assert.assertThat;
40 import static org.junit.Assert.assertTrue;
41 import static org.mockito.ArgumentMatchers.isA;
42 import static org.mockito.ArgumentMatchers.same;
43 import static org.mockito.Mockito.mock;
44 import static org.mockito.Mockito.verify;
45 import static org.mockito.Mockito.when;
46
47 public class TestBundledPluginLoader {
48
49 @Rule
50 public final ExpectedException expectedException = ExpectedException.none();
51 @Rule
52 public final CapturedLogging capturedLogging = new CapturedLogging(BundledPluginLoader.class);
53
54 private File pluginDir;
55
56 private static final String fooXml = "foo.xml";
57
58 @Before
59 public void createTemporaryDirectory() throws IOException {
60 pluginDir = PluginTestUtils.createTempDirectory(TestBundledPluginLoader.class);
61 }
62
63 @After
64 public void deleteTemporaryDirectory() throws Exception {
65 FileUtils.deleteDirectory(pluginDir);
66 pluginDir = null;
67 }
68
69 @Test
70 public void loaderFromZipWithUrlConstructorContainsExpectedFiles() throws IOException {
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 final File bundledZip = buildBundledZip();
79 final BundledPluginLoader loader = buildBundledPluginLoader(bundledZip, false);
80
81
82
83 assertLoaderContains(loader);
84 }
85
86 @Test
87 public void loaderFromDirectoryWithUrlConstructorContainsExpectedFiles() throws IOException {
88 validateLoaderFromDirectoryContainsExpectedFiles(true);
89 }
90
91 @Test
92 public void loaderFromDirectoryWithNonUrlConstructorContainsExpectedFiles() throws IOException {
93 validateLoaderFromDirectoryContainsExpectedFiles(false);
94 }
95
96 private void validateLoaderFromDirectoryContainsExpectedFiles(final boolean useUrlConstructor) throws IOException {
97 final File dir = PluginTestUtils.createTempDirectory(TestBundledPluginLoader.class);
98 FileUtils.writeStringToFile(new File(dir, "foo.txt"), "hello");
99
100 final BundledPluginLoader loader = buildBundledPluginLoader(dir, useUrlConstructor);
101 assertLoaderContains(loader, "foo.txt");
102 }
103
104 @Test
105 public void loaderFromFileListWithUrlConstructorContainsExpectedFiles() throws IOException {
106 validateLoaderFromFileListContainsExpectedFiles(true);
107 }
108
109 @Test
110 public void loaderFromFileListWithNonUrlConstructorContainsExpectedFiles() throws IOException {
111 validateLoaderFromFileListContainsExpectedFiles(false);
112 }
113
114 private void validateLoaderFromFileListContainsExpectedFiles(final boolean useUrlConstructor) throws IOException {
115 FileUtils.writeStringToFile(new File(pluginDir, "foo.txt"), "hello");
116 FileUtils.writeStringToFile(new File(pluginDir, "bar.txt"), "world");
117 final File listFile = new File(pluginDir, "bundled-plugins" + BundledPluginLoader.getListSuffix());
118 FileUtils.writeStringToFile(listFile, "foo.txt\nbar.txt");
119
120 final BundledPluginLoader loader = buildBundledPluginLoader(listFile, useUrlConstructor);
121 assertLoaderContains(loader, "foo.txt", "bar.txt");
122 }
123
124 @Test
125 public void loaderFromUnsupportedFileWithUrlConstructorContainsExpectedFiles() throws IOException {
126 validateLoaderFromUnsupportedFileContainsExpectedFiles(true);
127 }
128
129 @Test
130 public void loaderFromUnsupportedFileWithNonUrlConstructorContainsExpectedFiles() throws IOException {
131 validateLoaderFromUnsupportedFileContainsExpectedFiles(false);
132 }
133
134 private void validateLoaderFromUnsupportedFileContainsExpectedFiles(final boolean useUrlConstructor) throws IOException {
135 final File unsupportedFile = new File(pluginDir, "notASuitableFile.unknown-suffix");
136 FileUtils.writeStringToFile(unsupportedFile, "Some\nRandom\nContent\n");
137
138 final BundledPluginLoader loader = buildBundledPluginLoader(unsupportedFile, useUrlConstructor);
139 assertLoaderContains(loader);
140 }
141
142 @Test
143 public void loaderScannerDoesNotRemoveUnderlyingFiles() throws IOException {
144 final File bundledZip = buildBundledZip();
145
146
147 final BundledPluginLoader loader = buildBundledPluginLoader(bundledZip, true);
148 final Scanner scanner = loader.scanner;
149 assertLoaderContains(loader, fooXml);
150 final Collection<DeploymentUnit> units = scanner.getDeploymentUnits();
151 final int countBefore = units.size();
152 int found = 0;
153 for (final DeploymentUnit unit : units) {
154 final File file = unit.getPath();
155 if (fooXml.equals(file.getName())) {
156 found += 1;
157 assertTrue(file.exists());
158 scanner.remove(unit);
159
160 assertTrue(file.exists());
161 }
162 }
163
164 assertEquals(1, found);
165
166 assertEquals(countBefore, scanner.getDeploymentUnits().size());
167 }
168
169 @Test
170 public void loaderRemoveDoesUninstallPlugin() throws IOException {
171 final File bundledZip = buildBundledZip();
172 final PluginInternal mockPlugin = mock(PluginInternal.class);
173 final PluginFactory pluginFactory = mock(PluginFactory.class);
174 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
175
176 final String key = "plugin.key.foo";
177 final ArgumentCaptor<PluginArtifact> pluginArtifact = ArgumentCaptor.forClass(PluginArtifact.class);
178
179 when(mockPlugin.getKey()).thenReturn(key);
180 when(mockPlugin.isDeleteable()).thenReturn(true);
181 when(mockPlugin.isUninstallable()).thenReturn(true);
182 when(pluginFactory.canCreate(isA(PluginArtifact.class))).thenReturn(key);
183 when(pluginFactory.create(pluginArtifact.capture(), same(moduleDescriptorFactory))).thenReturn(mockPlugin);
184
185
186 final BundledPluginLoader loader = buildBundledPluginLoader(bundledZip, true, pluginFactory);
187 final Collection<Plugin> plugins = Lists.newArrayList(loader.loadAllPlugins(moduleDescriptorFactory));
188 assertEquals(1, plugins.size());
189
190 final Plugin bundledPlugin = Iterables.getOnlyElement(plugins);
191 assertEquals(key, bundledPlugin.getKey());
192 assertTrue(bundledPlugin.isDeleteable());
193 verify(mockPlugin).isDeleteable();
194 verify(mockPlugin).setBundledPlugin(true);
195
196
197 loader.removePlugin(bundledPlugin);
198 verify(mockPlugin).isUninstallable();
199 verify(mockPlugin).uninstall();
200
201 assertTrue(pluginArtifact.getValue().toFile().exists());
202
203
204
205
206
207
208
209 expectedException.expect(PluginException.class);
210 loader.removePlugin(bundledPlugin);
211 }
212
213 @Test
214 public void pluginArtifactsAllowReference() throws IOException {
215
216 final File dir = PluginTestUtils.createTempDirectory(TestBundledPluginLoader.class);
217 final File bundleList = new File(dir, "bundled-plugins" + BundledPluginLoader.getListSuffix());
218 FileUtils.writeStringToFile(bundleList, "some.jar\n");
219 final Plugin mockPlugin = mock(Plugin.class);
220 final PluginFactory pluginFactory = mock(PluginFactory.class);
221 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
222
223 final String key = "plugin.key.foo";
224 final ArgumentCaptor<PluginArtifact> pluginArtifactCaptor = ArgumentCaptor.forClass(PluginArtifact.class);
225
226 when(mockPlugin.getKey()).thenReturn(key);
227 when(pluginFactory.canCreate(isA(PluginArtifact.class))).thenReturn(key);
228 when(pluginFactory.create(pluginArtifactCaptor.capture(), same(moduleDescriptorFactory))).thenReturn(mockPlugin);
229
230
231 final BundledPluginLoader loader = buildBundledPluginLoader(bundleList, false, pluginFactory);
232 loader.loadAllPlugins(moduleDescriptorFactory);
233 final PluginArtifact pluginArtifact = pluginArtifactCaptor.getValue();
234 assertThat(pluginArtifact.getReferenceMode(), is(ReferenceMode.PERMIT_REFERENCE));
235 }
236
237 @Test
238 public void postProcessWarnsAboutInvalidPlugin() throws IOException {
239 final Plugin plugin = mock(Plugin.class);
240 when(plugin.toString()).thenReturn("blargh");
241
242 final BundledPluginLoader bundledPluginLoader = buildBundledPluginLoader(buildBundledZip(), true);
243
244 bundledPluginLoader.postProcess(plugin);
245 assertThat(capturedLogging, didLogWarn("blargh", Plugin.class.getCanonicalName()));
246 }
247
248 private File buildBundledZip() throws IOException {
249 return new PluginJarBuilder("bundledPlugins")
250 .addResource(fooXml, "<foo/>")
251 .buildWithNoManifest();
252 }
253
254 private BundledPluginLoader buildBundledPluginLoader(
255 final File bundledPlugins,
256 final boolean useUrlConstructor,
257 final PluginFactory... pluginFactories)
258 throws IOException {
259 final List<PluginFactory> pluginFactoryList = Arrays.asList(pluginFactories);
260 final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
261 if (useUrlConstructor) {
262 final URL bundledPluginsUrl = bundledPlugins.toURI().toURL();
263 return new BundledPluginLoader(bundledPluginsUrl, pluginDir, pluginFactoryList, pluginEventManager);
264 } else {
265 return new BundledPluginLoader(bundledPlugins, pluginFactoryList, pluginEventManager);
266 }
267 }
268
269 private void assertLoaderContains(final BundledPluginLoader loader, final String... expectedEntries) {
270 final Collection<DeploymentUnit> scanned = loader.scanner.scan();
271 final Iterable<String> actualEntries = Iterables.transform(scanned, new Function<DeploymentUnit, String>() {
272 @Override
273 public String apply(final DeploymentUnit unit) {
274 return unit.getPath().getName();
275 }
276 });
277 assertThat(actualEntries, containsInAnyOrder(expectedEntries));
278 }
279 }