1 package com.atlassian.plugin.osgi.factory;
2
3 import com.atlassian.plugin.JarPluginArtifact;
4 import com.atlassian.plugin.ModuleDescriptor;
5 import com.atlassian.plugin.ModuleDescriptorFactory;
6 import com.atlassian.plugin.Plugin;
7 import com.atlassian.plugin.PluginAccessor;
8 import com.atlassian.plugin.PluginInformation;
9 import com.atlassian.plugin.PluginParseException;
10 import com.atlassian.plugin.XmlPluginArtifact;
11 import com.atlassian.plugin.event.impl.DefaultPluginEventManager;
12 import com.atlassian.plugin.osgi.container.OsgiContainerManager;
13 import com.atlassian.plugin.osgi.container.impl.DefaultOsgiPersistentCache;
14 import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
15 import com.atlassian.plugin.test.PluginJarBuilder;
16 import com.atlassian.plugin.test.PluginTestUtils;
17 import com.google.common.collect.ImmutableList;
18 import com.google.common.collect.ImmutableMap;
19 import com.mockobjects.dynamic.C;
20 import com.mockobjects.dynamic.Mock;
21 import org.apache.commons.io.FileUtils;
22 import org.dom4j.Element;
23 import org.junit.After;
24 import org.junit.Before;
25 import org.junit.Test;
26 import org.osgi.framework.Bundle;
27 import org.osgi.framework.BundleContext;
28 import org.osgi.framework.Constants;
29 import org.osgi.service.packageadmin.PackageAdmin;
30 import org.osgi.util.tracker.ServiceTracker;
31
32 import java.io.File;
33 import java.io.IOException;
34 import java.util.Collections;
35 import java.util.Dictionary;
36 import java.util.Hashtable;
37
38 import static com.atlassian.plugin.ReferenceMode.FORBID_REFERENCE;
39 import static com.atlassian.plugin.ReferenceMode.PERMIT_REFERENCE;
40 import static org.hamcrest.Matchers.instanceOf;
41 import static org.hamcrest.Matchers.is;
42 import static org.hamcrest.Matchers.nullValue;
43 import static org.hamcrest.Matchers.sameInstance;
44 import static org.hamcrest.core.IsEqual.equalTo;
45 import static org.junit.Assert.assertEquals;
46 import static org.junit.Assert.assertNotNull;
47 import static org.junit.Assert.assertNull;
48 import static org.junit.Assert.assertThat;
49 import static org.junit.Assert.assertTrue;
50 import static org.junit.Assert.fail;
51 import static org.mockito.ArgumentMatchers.any;
52 import static org.mockito.ArgumentMatchers.eq;
53 import static org.mockito.Mockito.mock;
54 import static org.mockito.Mockito.verify;
55 import static org.mockito.Mockito.when;
56
57 public class TestOsgiPluginFactory {
58 OsgiPluginFactory factory;
59
60 private File tmpDir;
61 private File jar;
62 private OsgiContainerManager osgiContainerManager;
63 private Mock mockBundle;
64 private Mock mockSystemBundle;
65
66 @Before
67 public void setUp() throws IOException {
68 tmpDir = PluginTestUtils.createTempDirectory(TestOsgiPluginFactory.class);
69 osgiContainerManager = mock(OsgiContainerManager.class);
70
71 final ServiceTracker tracker = mock(ServiceTracker.class);
72 when(tracker.getServices()).thenReturn(new Object[0]);
73 when(osgiContainerManager.getServiceTracker(ModuleDescriptorFactory.class.getName())).thenReturn(tracker);
74
75 factory = new OsgiPluginFactory(PluginAccessor.Descriptor.FILENAME, Collections.emptySet(), new DefaultOsgiPersistentCache(tmpDir), osgiContainerManager, new DefaultPluginEventManager());
76 jar = new PluginJarBuilder("someplugin").addPluginInformation("plugin.key", "My Plugin", "1.0").build();
77
78 mockBundle = new Mock(Bundle.class);
79 final Dictionary<String, String> dict = new Hashtable<>();
80 dict.put(Constants.BUNDLE_DESCRIPTION, "desc");
81 dict.put(Constants.BUNDLE_VERSION, "1.0");
82 mockBundle.matchAndReturn("getHeaders", dict);
83
84 mockSystemBundle = new Mock(Bundle.class);
85 final Dictionary<String, String> sysDict = new Hashtable<>();
86 sysDict.put(Constants.BUNDLE_DESCRIPTION, "desc");
87 sysDict.put(Constants.BUNDLE_VERSION, "1.0");
88 mockSystemBundle.matchAndReturn("getHeaders", sysDict);
89 mockSystemBundle.matchAndReturn("getLastModified", System.currentTimeMillis());
90 mockSystemBundle.matchAndReturn("getSymbolicName", "system.bundle");
91
92 final Mock mockSysContext = new Mock(BundleContext.class);
93 mockSystemBundle.matchAndReturn("getBundleContext", mockSysContext.proxy());
94
95 mockSysContext.matchAndReturn("getServiceReference", C.ANY_ARGS, null);
96 mockSysContext.matchAndReturn("getService", C.ANY_ARGS, new Mock(PackageAdmin.class).proxy());
97 }
98
99 @After
100 public void tearDown() throws IOException {
101 factory = null;
102 FileUtils.cleanDirectory(tmpDir);
103 jar.delete();
104 }
105
106 @Test
107 public void createOsgiPlugin() throws PluginParseException {
108 mockBundle.expectAndReturn("getSymbolicName", "plugin.key");
109 when(osgiContainerManager.getHostComponentRegistrations()).thenReturn(ImmutableList.of());
110 when(osgiContainerManager.getBundles()).thenReturn(new Bundle[]{(Bundle) mockSystemBundle.proxy()});
111 final Plugin plugin = factory.create(new JarPluginArtifact(jar), (ModuleDescriptorFactory) new Mock(ModuleDescriptorFactory.class).proxy());
112 assertNotNull(plugin);
113 assertTrue(plugin instanceof OsgiPlugin);
114 }
115
116 @Test
117 public void createOsgiPluginWithBadVersion() throws PluginParseException, IOException {
118 jar = new PluginJarBuilder("someplugin").addPluginInformation("plugin.key", "My Plugin", "beta.1.0").build();
119 mockBundle.expectAndReturn("getSymbolicName", "plugin.key");
120 when(osgiContainerManager.getHostComponentRegistrations()).thenReturn(ImmutableList.of());
121 when(osgiContainerManager.getBundles()).thenReturn(new Bundle[]{(Bundle) mockSystemBundle.proxy()});
122 try {
123 factory.create(new JarPluginArtifact(jar), (ModuleDescriptorFactory) new Mock(ModuleDescriptorFactory.class).proxy());
124 fail("Should have complained about osgi version");
125 } catch (final PluginParseException ex) {
126
127 }
128 }
129
130 @Test
131 public void createOsgiPluginWithBadVersion2() throws PluginParseException, IOException {
132 jar = new PluginJarBuilder("someplugin").addPluginInformation("plugin.key", "My Plugin", "3.2-rc1").build();
133 mockBundle.expectAndReturn("getSymbolicName", "plugin.key");
134 when(osgiContainerManager.getHostComponentRegistrations()).thenReturn(ImmutableList.of());
135 when(osgiContainerManager.getBundles()).thenReturn(new Bundle[]{(Bundle) mockSystemBundle.proxy()});
136 final Plugin plugin = factory.create(
137 new JarPluginArtifact(jar), (ModuleDescriptorFactory) new Mock(ModuleDescriptorFactory.class).proxy());
138 assertTrue(plugin instanceof OsgiPlugin);
139 }
140
141 @Test
142 public void createOsgiPluginWithManifestKey() throws PluginParseException, IOException {
143 mockBundle.expectAndReturn("getSymbolicName", "plugin.key");
144 when(osgiContainerManager.getHostComponentRegistrations()).thenReturn(ImmutableList.of());
145 when(osgiContainerManager.getBundles()).thenReturn(new Bundle[]{(Bundle) mockSystemBundle.proxy()});
146
147 final File pluginFile = new PluginJarBuilder("loadwithxml")
148 .manifest(new ImmutableMap.Builder<String, String>()
149 .put(OsgiPlugin.ATLASSIAN_PLUGIN_KEY, "somekey")
150 .put(Constants.BUNDLE_VERSION, "1.0")
151 .put(Constants.BUNDLE_NAME, "My awesome plugin")
152 .put(Constants.BUNDLE_DESCRIPTION, "Bundle Description")
153 .put(Constants.BUNDLE_VENDOR, "My vendor Name")
154 .build())
155 .build();
156
157 final Plugin plugin = factory.create(new JarPluginArtifact(pluginFile), (ModuleDescriptorFactory) new Mock(ModuleDescriptorFactory.class).proxy());
158 assertNotNull(plugin);
159 assertTrue(plugin instanceof OsgiPlugin);
160 }
161
162 @Test
163 public void createOsgiPluginWithManifestKeyAndDescriptorSkippingTransform() throws PluginParseException, IOException {
164 final File jar = new PluginJarBuilder("someplugin")
165 .addPluginInformation("plugin.key", "My Plugin", "1.0")
166 .manifest(new ImmutableMap.Builder<String, String>()
167 .put(OsgiPlugin.ATLASSIAN_PLUGIN_KEY, "somekey")
168 .put(Constants.BUNDLE_VERSION, "1.0")
169 .build())
170 .build();
171 mockBundle.expectAndReturn("getSymbolicName", "plugin.key");
172 mockBundle.expectAndReturn("getHeaders", new Hashtable() {{
173 put(OsgiPlugin.ATLASSIAN_PLUGIN_KEY, "plugin.key");
174 put(Constants.BUNDLE_VERSION, "1.0");
175 }});
176 when(osgiContainerManager.getHostComponentRegistrations()).thenReturn(
177 Collections.emptyList());
178 when(osgiContainerManager.getBundles()).thenReturn(new Bundle[]{(Bundle) mockSystemBundle.proxy()});
179 when(osgiContainerManager.installBundle(jar, FORBID_REFERENCE)).thenReturn((Bundle) mockBundle.proxy());
180 final Plugin plugin = factory.create(new JarPluginArtifact(jar), (ModuleDescriptorFactory) new Mock(ModuleDescriptorFactory.class).proxy());
181 assertNotNull(plugin);
182 assertTrue(plugin instanceof OsgiPlugin);
183
184 plugin.install();
185 verify(osgiContainerManager).installBundle(jar, FORBID_REFERENCE);
186 }
187
188 @Test
189 public void pluginInformationAndPluginNameIsExtractedFromManifest() throws IOException {
190 final PluginInformation expectedPluginInformation = createPluginInformation(
191 "My Description", "My Awesome Vendor Name", "1.0");
192 when(osgiContainerManager.getHostComponentRegistrations()).thenReturn(ImmutableList.of());
193 when(osgiContainerManager.getBundles()).thenReturn(new Bundle[]{(Bundle) mockSystemBundle.proxy()});
194
195 final String pluginName = "My awesome plugin";
196
197 final Plugin plugin = createPlugin(pluginName, expectedPluginInformation);
198 assertNotNull(plugin);
199 assertTrue(plugin instanceof OsgiPlugin);
200
201 assertThat(plugin.getName(), equalTo(pluginName));
202
203 final PluginInformation actualPluginInformation = plugin.getPluginInformation();
204 assertThat(actualPluginInformation.getDescription(), equalTo(expectedPluginInformation.getDescription()));
205 assertThat(actualPluginInformation.getVendorName(), equalTo(expectedPluginInformation.getVendorName()));
206 assertThat(actualPluginInformation.getVersion(), equalTo(expectedPluginInformation.getVersion()));
207 }
208
209 @Test
210 public void shouldLoadPluginIfManifestDoesNotHaveOptionalInformation() throws IOException {
211 final PluginInformation pluginInformation = createPluginInformation("", "", "1.0");
212 when(osgiContainerManager.getHostComponentRegistrations()).thenReturn(ImmutableList.of());
213 when(osgiContainerManager.getBundles()).thenReturn(new Bundle[]{(Bundle) mockSystemBundle.proxy()});
214
215 final String pluginName = "My Awesome Plugin";
216 final Plugin plugin = createPlugin(pluginName, pluginInformation);
217
218 assertNotNull(plugin);
219 assertTrue(plugin instanceof OsgiPlugin);
220 assertThat(plugin.getName(), equalTo(pluginName));
221 }
222
223 private Plugin createPlugin(final String pluginName, final PluginInformation pluginInformation) throws IOException {
224 final File pluginFile = new PluginJarBuilder("loadwithxml")
225 .manifest(new ImmutableMap.Builder<String, String>()
226 .put(OsgiPlugin.ATLASSIAN_PLUGIN_KEY, pluginName)
227 .put(Constants.BUNDLE_VERSION, pluginInformation.getVersion())
228 .put(Constants.BUNDLE_NAME, pluginName)
229 .put(Constants.BUNDLE_DESCRIPTION, pluginInformation.getDescription())
230 .put(Constants.BUNDLE_VENDOR, pluginInformation.getVendorName())
231 .build())
232 .build();
233
234 return factory.create(new JarPluginArtifact(pluginFile), (ModuleDescriptorFactory) new Mock(ModuleDescriptorFactory.class).proxy());
235 }
236
237 private PluginInformation createPluginInformation(final String description, final String vendorName, final String version) {
238 final PluginInformation pluginInformation = new PluginInformation();
239 pluginInformation.setDescription(description);
240 pluginInformation.setVendorName(vendorName);
241 pluginInformation.setVersion(version);
242 return pluginInformation;
243 }
244
245 @Test
246 public void createOsgiPluginWithManifestKeyNoVersion() throws PluginParseException, IOException {
247 mockBundle.expectAndReturn("getSymbolicName", "plugin.key");
248 when(osgiContainerManager.getHostComponentRegistrations()).thenReturn(ImmutableList.of());
249 when(osgiContainerManager.getBundles()).thenReturn(new Bundle[]{(Bundle) mockSystemBundle.proxy()});
250
251 final File pluginFile = new PluginJarBuilder("loadwithxml")
252 .manifest(new ImmutableMap.Builder<String, String>()
253 .put(OsgiPlugin.ATLASSIAN_PLUGIN_KEY, "somekey")
254 .build())
255 .build();
256
257 try {
258 factory.create(new JarPluginArtifact(pluginFile), (ModuleDescriptorFactory) new Mock(ModuleDescriptorFactory.class).proxy());
259 fail("Should have failed due to no version");
260 } catch (final NullPointerException ex) {
261
262 }
263 }
264
265 @Test
266 public void canLoadWithXml() throws PluginParseException, IOException {
267 final File plugin = new PluginJarBuilder("loadwithxml").addPluginInformation("foo.bar", "", "1.0").build();
268 final String key = factory.canCreate(new JarPluginArtifact(plugin));
269 assertEquals("foo.bar", key);
270 }
271
272 @Test
273 public void canLoadWithXmlArtifact() throws PluginParseException, IOException {
274 final File xmlFile = new File(tmpDir, "plugin.xml");
275 FileUtils.writeStringToFile(xmlFile, "<somexml />");
276 final String key = factory.canCreate(new XmlPluginArtifact(xmlFile));
277 assertNull(key);
278 }
279
280 @Test
281 public void canLoadJarWithNoManifest() throws PluginParseException, IOException {
282 final File plugin = new PluginJarBuilder("loadwithxml")
283 .addResource("foo.xml", "<foo/>")
284 .buildWithNoManifest();
285 final String key = factory.canCreate(new JarPluginArtifact(plugin));
286 assertNull(key);
287 }
288
289 @Test
290 public void canLoadNoXml() throws PluginParseException, IOException {
291 final File plugin = new PluginJarBuilder("loadwithxml").build();
292 final String key = factory.canCreate(new JarPluginArtifact(plugin));
293 assertNull(key);
294 }
295
296 @Test
297 public void canLoadNoXmlButWithManifestEntry() throws PluginParseException, IOException {
298 final File plugin = new PluginJarBuilder("loadwithxml")
299 .manifest(new ImmutableMap.Builder<String, String>()
300 .put(OsgiPlugin.ATLASSIAN_PLUGIN_KEY, "somekey")
301 .put(Constants.BUNDLE_VERSION, "1.0")
302 .put("Spring-Context", "*")
303 .build())
304 .build();
305 final String key = factory.canCreate(new JarPluginArtifact(plugin));
306 assertEquals("somekey", key);
307 }
308
309 @Test
310 public void canLoadNoXmlButWithManifestEntryNoVersion() throws PluginParseException, IOException {
311 final File plugin = new PluginJarBuilder("loadwithxml")
312 .manifest(new ImmutableMap.Builder<String, String>()
313 .put(OsgiPlugin.ATLASSIAN_PLUGIN_KEY, "somekey")
314 .build())
315 .build();
316 final String key = factory.canCreate(new JarPluginArtifact(plugin));
317 assertNull(key);
318 }
319
320 @Test
321 public void canLoadWithXmlVersion3() throws Exception {
322 final File plugin = new PluginJarBuilder("loadwithxml").addPluginInformation("foo.bar", "", "1.0", 3).build();
323 final String key = factory.canCreate(new JarPluginArtifact(plugin));
324 assertNull(key);
325 }
326
327 @Test
328 public void transformedArtifactAllowsReferenceInstall() {
329 mockBundle.expectAndReturn("getSymbolicName", "plugin.key");
330
331 ((Bundle) mockBundle.proxy()).getHeaders().put(OsgiPlugin.ATLASSIAN_PLUGIN_KEY, "plugin.key");
332
333 when(osgiContainerManager.getHostComponentRegistrations()).thenReturn(ImmutableList.of());
334 when(osgiContainerManager.getBundles()).thenReturn(new Bundle[]{(Bundle) mockSystemBundle.proxy()});
335
336 when(osgiContainerManager.installBundle(any(File.class), eq(PERMIT_REFERENCE)))
337 .thenReturn((Bundle) mockBundle
338 .proxy());
339 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
340 final Plugin plugin = factory.create(new JarPluginArtifact(jar), moduleDescriptorFactory);
341 plugin.install();
342 assertThat(plugin, instanceOf(OsgiPlugin.class));
343 final Bundle actualBundle = ((OsgiPlugin) plugin).getBundle();
344 assertThat(actualBundle, sameInstance(mockBundle.proxy()));
345
346 verify(osgiContainerManager).installBundle(any(File.class), eq(PERMIT_REFERENCE));
347 }
348
349 @Test
350 public void canLoadExtraModuleDescriptors() throws Exception {
351 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
352 final File pluginFile = new PluginJarBuilder("loadextramodules")
353 .manifest(new ImmutableMap.Builder<String, String>()
354 .put(OsgiPlugin.ATLASSIAN_PLUGIN_KEY, "pluginKey")
355 .put(Constants.BUNDLE_VERSION, "3")
356 .put(Constants.BUNDLE_NAME, "bundleName")
357 .put(Constants.BUNDLE_DESCRIPTION, "bundleDescription")
358 .put(Constants.BUNDLE_VENDOR, "Atlassian")
359 .put("Atlassian-Scan-Folders", "META-INF/atlassian")
360 .build())
361 .addResource("META-INF/atlassian/foo.xml", "<atlassian-plugin><rest key=\"sample\" path=\"/sample\" version=\"1\"/></atlassian-plugin>")
362 .addResource("META-INF/atlassian/bar.xml", "<atlassian-plugin/>")
363 .addPluginInformation("pluginKey", "1.1", "3")
364 .build();
365 when(osgiContainerManager.getHostComponentRegistrations()).thenReturn(ImmutableList.of());
366 when(osgiContainerManager.getBundles()).thenReturn(new Bundle[]{(Bundle) mockSystemBundle.proxy()});
367 final Plugin plugin = factory.create(new JarPluginArtifact(pluginFile), moduleDescriptorFactory);
368 assertThat(plugin.getModuleDescriptor("sample"), instanceOf(ModuleDescriptor.class));
369
370 }
371
372 @Test
373 public void createModuleIncorrectPluginType() {
374 final Plugin plugin = mock(Plugin.class);
375 final Element module = mock(Element.class);
376 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
377
378 assertThat(factory.createModule(plugin, module, moduleDescriptorFactory), nullValue());
379 }
380
381 @Test
382 public void createModule() throws IllegalAccessException, ClassNotFoundException, InstantiationException {
383 final OsgiPlugin plugin = mock(OsgiPlugin.class);
384 final Element module = mock(Element.class);
385 final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class);
386 final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
387
388 when(module.getName()).thenReturn("william");
389 when(moduleDescriptorFactory.hasModuleDescriptor("william")).thenReturn(true);
390 when(moduleDescriptorFactory.getModuleDescriptor("william")).thenReturn(moduleDescriptor);
391
392 assertThat(factory.createModule(plugin, module, moduleDescriptorFactory), is(moduleDescriptor));
393 }
394 }