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