1 package com.atlassian.plugin.osgi.factory;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.net.URL;
7 import java.net.URLConnection;
8 import java.net.URLStreamHandler;
9 import java.util.Hashtable;
10 import java.util.concurrent.TimeUnit;
11
12 import com.atlassian.plugin.IllegalPluginStateException;
13 import com.atlassian.plugin.JarPluginArtifact;
14 import com.atlassian.plugin.Plugin;
15 import com.atlassian.plugin.PluginArtifact;
16 import com.atlassian.plugin.osgi.container.OsgiContainerException;
17 import com.atlassian.plugin.osgi.container.OsgiContainerManager;
18 import com.atlassian.plugin.test.PluginJarBuilder;
19
20 import com.google.common.collect.ImmutableMap;
21
22 import org.junit.Before;
23 import org.junit.Rule;
24 import org.junit.Test;
25 import org.junit.rules.ExpectedException;
26 import org.junit.runner.RunWith;
27 import org.mockito.Mock;
28 import org.mockito.runners.MockitoJUnitRunner;
29 import org.osgi.framework.Bundle;
30 import org.osgi.framework.BundleContext;
31 import org.osgi.framework.Constants;
32
33 import static com.atlassian.plugin.PluginArtifact.AllowsReference.ReferenceMode.PERMIT_REFERENCE;
34 import static org.hamcrest.MatcherAssert.assertThat;
35 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
36 import static org.hamcrest.Matchers.is;
37 import static org.hamcrest.Matchers.lessThanOrEqualTo;
38 import static org.hamcrest.Matchers.nullValue;
39 import static org.hamcrest.Matchers.sameInstance;
40 import static org.mockito.Mockito.mock;
41 import static org.mockito.Mockito.verify;
42 import static org.mockito.Mockito.when;
43 import static org.mockito.Mockito.withSettings;
44
45
46
47
48
49
50
51
52
53
54
55
56 @RunWith (MockitoJUnitRunner.class)
57 public abstract class TestOsgiBundlePlugin
58 {
59 public static final String DESCRIPTION = "A Bundle for Testing";
60 public static final String NAME = "Test Bundle";
61 public static final String SYMBOLIC_NAME = TestOsgiBundlePlugin.class.getName() + ".testBundle";
62 public static final String VENDOR = "Some Bundle Vendor";
63 public static final String VERSION = "1.2";
64 public static final String PLUGIN_KEY = SYMBOLIC_NAME + "-" + VERSION;
65
66 private long testStarted;
67
68 @Mock Bundle bundle;
69 PluginArtifact pluginArtifact;
70
71 OsgiBundlePlugin osgiBundlePlugin;
72
73 @Before
74 public void setUp() throws Exception
75 {
76 testStarted = System.currentTimeMillis();
77 when(bundle.getHeaders()).thenReturn(new Hashtable<String, String>(getBundleManifest()));
78 pluginArtifact = getBundleJarPluginArtifact();
79 osgiBundlePlugin = getOsgiBundlePlugin();
80 }
81
82 protected abstract OsgiBundlePlugin getOsgiBundlePlugin();
83
84 @Test
85 public void constructorConfiguresPluginCorrectly()
86 {
87 assertThatPluginIsConfigured(osgiBundlePlugin);
88 }
89
90 @Test
91 public void getDateLoadedIsRecent()
92 {
93 final long timeLoaded = osgiBundlePlugin.getDateLoaded().getTime();
94 final long now = System.currentTimeMillis();
95 assertThat(timeLoaded, greaterThanOrEqualTo(testStarted));
96 assertThat(timeLoaded, lessThanOrEqualTo(now));
97 }
98
99 @Test
100 public void getDateInstalledMatchesFileTimestamp()
101 {
102 final long timeInstalled = osgiBundlePlugin.getDateInstalled().getTime();
103 assertThat(timeInstalled, is(pluginArtifact.toFile().lastModified()));
104 }
105
106 @Test
107 public void isUninstallableIsTrue()
108 {
109 assertThat(osgiBundlePlugin.isUninstallable(), is(true));
110 }
111
112 @Test
113 public void isDeleteableIsTrue()
114 {
115 assertThat(osgiBundlePlugin.isDeleteable(), is(true));
116 }
117
118 @Test
119 public void isDynamicallyLoadedIsTrue()
120 {
121 assertThat(osgiBundlePlugin.isDynamicallyLoaded(), is(true));
122 }
123
124 @Test
125 public void loadClassForwardsToBundle() throws Exception
126 {
127 osgiBundlePlugin.install();
128
129 final String className = "java.lang.String";
130 final Class expected = String.class;
131 when(bundle.loadClass(className)).thenReturn(expected);
132 final Class actual = (Class) osgiBundlePlugin.loadClass(className, getClass());
133 verify(bundle).loadClass(className);
134 assertThat(actual, sameInstance(expected));
135 }
136
137 @Test
138 public void getResourceForwardsToBundle() throws Exception
139 {
140 osgiBundlePlugin.install();
141 final String resourceName = "someResource";
142 final URL expected = new URL("mock", null, 0, "some/resource", mock(URLStreamHandler.class));
143 when(bundle.getResource(resourceName)).thenReturn(expected);
144 final URL actual = osgiBundlePlugin.getResource(resourceName);
145 verify(bundle).getResource(resourceName);
146 assertThat(actual, sameInstance(expected));
147 }
148
149 @Test
150 public void getResourceAsStreamForwardsToBundle() throws Exception
151 {
152 osgiBundlePlugin.install();
153 final String resourceName = "someResource";
154 final InputStream expected = mock(InputStream.class);
155 final URLConnection urlConnection = mock(URLConnection.class);
156 when(urlConnection.getInputStream()).thenReturn(expected);
157 final URLStreamHandler urlStreamHandler = new URLStreamHandler()
158 {
159 @Override
160 protected URLConnection openConnection(final URL u) throws IOException
161 {
162 return urlConnection;
163 }
164 };
165 final URL resourceUrl = new URL("mock", null, 0, "some/resource", urlStreamHandler);
166 when(bundle.getResource(resourceName)).thenReturn(resourceUrl);
167 final InputStream actual = osgiBundlePlugin.getResourceAsStream(resourceName);
168 verify(bundle).getResource(resourceName);
169 assertThat(actual, sameInstance(expected));
170 }
171
172 @Test
173 public void getClassLoaderReturnsClassLoaderThatUsesBundle() throws Exception
174 {
175 osgiBundlePlugin.install();
176
177 final String className = "com.atlassian.plugin.test.some.fake.class.name";
178
179 final Class expected = String.class;
180 when(bundle.loadClass(className)).thenReturn(expected);
181 final Class actual = osgiBundlePlugin.getClassLoader().loadClass(className);
182 verify(bundle).loadClass(className);
183 assertThat(actual, sameInstance(expected));
184 }
185
186 @Test
187 public void getPluginArtifactReturnsPluginArtifact()
188 {
189 assertThat(osgiBundlePlugin.getPluginArtifact(), is(pluginArtifact));
190 }
191
192 @Test
193 public void executeBasicLifeCycle() throws Exception
194 {
195 final BundleContext bundleContext = mock(BundleContext.class);
196 when(bundle.getBundleContext()).thenReturn(bundleContext);
197
198 osgiBundlePlugin.install();
199 osgiBundlePlugin.enable();
200 verify(bundle).start();
201 when(bundle.getState()).thenReturn(Bundle.ACTIVE);
202 osgiBundlePlugin.disable();
203 verify(bundle).stop();
204 osgiBundlePlugin.uninstall();
205 }
206
207 static void assertThatPluginIsConfigured(final Plugin plugin)
208 {
209 assertThat(plugin.getKey(), is(PLUGIN_KEY));
210 assertThat(plugin.getPluginInformation().getDescription(), is(DESCRIPTION));
211 assertThat(plugin.getName(), is(NAME));
212 assertThat(plugin.getPluginInformation().getVendorName(), is(VENDOR));
213 assertThat(plugin.getPluginInformation().getVersion(), is(VERSION));
214 assertThat(plugin.getI18nNameKey(), nullValue());
215 }
216
217 static PluginArtifact getBundleJarPluginArtifact() throws IOException
218 {
219
220 final File bundleJar = new PluginJarBuilder("somebundle")
221 .manifest(getBundleManifest())
222 .build();
223
224 if (!bundleJar.setLastModified(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)))
225 {
226 throw new IOException("Test broken, cannot backdate bundleJar '" + bundleJar + "'");
227 }
228 return new JarPluginArtifact(bundleJar);
229 }
230
231 static ImmutableMap<String, String> getBundleManifest()
232 {
233 return ImmutableMap.<String, String>builder()
234 .put(Constants.BUNDLE_DESCRIPTION, DESCRIPTION)
235 .put(Constants.BUNDLE_NAME, NAME)
236 .put(Constants.BUNDLE_SYMBOLICNAME, SYMBOLIC_NAME)
237 .put(Constants.BUNDLE_VENDOR, VENDOR)
238 .put(Constants.BUNDLE_VERSION, VERSION)
239 .build();
240 }
241
242 public static class Modern extends TestOsgiBundlePlugin
243 {
244 @Rule
245 public ExpectedException expectedException = ExpectedException.none();
246
247 @Mock private OsgiContainerManager osgiContainerManager;
248
249 @Override
250 public void setUp() throws Exception
251 {
252 super.setUp();
253
254 when(osgiContainerManager.installBundle(pluginArtifact.toFile())).thenReturn(bundle);
255 }
256
257 @Override
258 protected OsgiBundlePlugin getOsgiBundlePlugin()
259 {
260 return new OsgiBundlePlugin(osgiContainerManager, PLUGIN_KEY, pluginArtifact);
261 }
262
263 @Test
264 public void loadClassThrowsBeforeInstall() throws Exception
265 {
266 expectedException.expect(IllegalPluginStateException.class);
267 osgiBundlePlugin.loadClass("java.lang.String", getClass());
268 }
269
270 @Test
271 public void getResourceThrowsBeforeInstall() throws Exception
272 {
273 expectedException.expect(IllegalPluginStateException.class);
274 osgiBundlePlugin.getResource("someResource");
275 }
276
277 @Test
278 public void getResourceAsStreamThrowsBeforeInstall() throws Exception
279 {
280 expectedException.expect(IllegalPluginStateException.class);
281 osgiBundlePlugin.getResourceAsStream("someResource");
282 }
283
284 @Test
285 public void getClassLoaderThrowsBeforeInstall() throws Exception
286 {
287 expectedException.expect(IllegalPluginStateException.class);
288 osgiBundlePlugin.getClassLoader();
289 }
290
291 @Test
292 public void loadClassThrowsAfterUninstall() throws Exception
293 {
294 osgiBundlePlugin.install();
295 osgiBundlePlugin.uninstall();
296 expectedException.expect(IllegalPluginStateException.class);
297 osgiBundlePlugin.loadClass("java.lang.String", getClass());
298 }
299
300 @Test
301 public void getResourceThrowsAfterUninstall() throws Exception
302 {
303 osgiBundlePlugin.install();
304 osgiBundlePlugin.uninstall();
305 expectedException.expect(IllegalPluginStateException.class);
306 osgiBundlePlugin.getResource("someResource");
307 }
308
309 @Test
310 public void getResourceAsStreamThrowsAfterUninstall() throws Exception
311 {
312 osgiBundlePlugin.install();
313 osgiBundlePlugin.uninstall();
314 expectedException.expect(IllegalPluginStateException.class);
315 osgiBundlePlugin.getResourceAsStream("someResource");
316 }
317
318 @Test
319 public void getClassLoaderThrowsAfterUninstall() throws Exception
320 {
321 osgiBundlePlugin.install();
322 osgiBundlePlugin.uninstall();
323 expectedException.expect(IllegalPluginStateException.class);
324 osgiBundlePlugin.getClassLoader();
325 }
326
327 @Test
328 public void installFailsIfOsgiContainerManagerInstallBundleFails()
329 {
330 final OsgiContainerException osgiContainerException = new OsgiContainerException("Intentional fail for test");
331 when(osgiContainerManager.installBundle(pluginArtifact.toFile())).thenThrow(osgiContainerException);
332
333
334 expectedException.expect(is(osgiContainerException));
335 osgiBundlePlugin.install();
336 }
337
338 @Test
339 public void pluginArtifactThatAllowsReferenceIsInstalledByReference() throws Exception
340 {
341 final File bundleJar = pluginArtifact.toFile();
342 pluginArtifact = new JarPluginArtifact(bundleJar, PERMIT_REFERENCE);
343 osgiContainerManager = mock(OsgiContainerManager.class,
344 withSettings().extraInterfaces(OsgiContainerManager.AllowsReferenceInstall.class));
345
346 when(((OsgiContainerManager.AllowsReferenceInstall) osgiContainerManager)
347 .installBundle(bundleJar, true)).thenReturn(bundle);
348 osgiBundlePlugin = new OsgiBundlePlugin(osgiContainerManager, PLUGIN_KEY, pluginArtifact);
349 osgiBundlePlugin.install();
350
351
352 osgiBundlePlugin.getClassLoader();
353 }
354 }
355
356 public static class Legacy extends TestOsgiBundlePlugin
357 {
358 @Override
359 protected OsgiBundlePlugin getOsgiBundlePlugin()
360 {
361
362 return new OsgiBundlePlugin(bundle, PLUGIN_KEY, pluginArtifact);
363 }
364 }
365 }