1 package com.atlassian.plugin.osgi.container.felix;
2
3 import java.io.File;
4 import java.io.FilenameFilter;
5 import java.io.IOException;
6 import java.net.URISyntaxException;
7 import java.net.URL;
8 import java.util.ArrayList;
9 import java.util.Collection;
10 import java.util.List;
11 import java.util.concurrent.CountDownLatch;
12 import java.util.concurrent.TimeUnit;
13
14 import com.atlassian.plugin.event.impl.DefaultPluginEventManager;
15 import com.atlassian.plugin.osgi.container.OsgiContainerException;
16 import com.atlassian.plugin.osgi.container.OsgiContainerManager;
17 import com.atlassian.plugin.osgi.container.OsgiPersistentCache;
18 import com.atlassian.plugin.osgi.container.impl.DefaultOsgiPersistentCache;
19 import com.atlassian.plugin.osgi.container.impl.DefaultPackageScannerConfiguration;
20 import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
21 import com.atlassian.plugin.test.PluginJarBuilder;
22 import com.atlassian.plugin.test.PluginTestUtils;
23
24 import com.atlassian.plugin.util.PluginUtils;
25 import com.google.common.collect.ImmutableList;
26
27 import org.apache.commons.io.FileUtils;
28 import org.apache.felix.framework.cache.BundleArchive;
29 import org.hamcrest.Description;
30 import org.hamcrest.Matcher;
31 import org.hamcrest.TypeSafeMatcher;
32 import org.osgi.framework.Bundle;
33 import org.osgi.framework.BundleException;
34 import org.osgi.framework.Constants;
35 import org.osgi.framework.ServiceReference;
36 import org.osgi.service.packageadmin.PackageAdmin;
37 import org.osgi.util.tracker.ServiceTracker;
38 import org.osgi.util.tracker.ServiceTrackerCustomizer;
39
40 import junit.framework.TestCase;
41
42 import static org.hamcrest.Matchers.empty;
43 import static org.hamcrest.Matchers.hasSize;
44 import static org.hamcrest.Matchers.not;
45 import static org.junit.Assert.assertThat;
46
47 public class TestFelixOsgiContainerManager extends TestCase
48 {
49 private File tmpdir;
50 private OsgiPersistentCache osgiPersistentCache;
51 private FelixOsgiContainerManager felix;
52 private URL frameworkBundlesUrl = getClass().getResource("/nothing.zip");
53
54 @Override
55 public void setUp() throws Exception
56 {
57 super.setUp();
58 tmpdir = PluginTestUtils.createTempDirectory(TestFelixOsgiContainerManager.class);
59 osgiPersistentCache = new DefaultOsgiPersistentCache(tmpdir);
60 felix = new FelixOsgiContainerManager(frameworkBundlesUrl, osgiPersistentCache, new DefaultPackageScannerConfiguration(),
61 null, new DefaultPluginEventManager());
62 }
63
64 @Override
65 public void tearDown() throws Exception
66 {
67 if (felix != null && felix.isRunning())
68 {
69 for (Bundle bundle : felix.getBundles())
70 {
71 try
72 {
73 bundle.uninstall();
74 }
75 catch (BundleException ignored) {}
76 }
77 }
78 if (felix != null)
79 {
80 felix.stop();
81 felix.clearExportCache();
82 }
83 felix = null;
84 osgiPersistentCache = null;
85 tmpdir = null;
86 super.tearDown();
87 }
88
89 public void testDetectXercesOverride()
90 {
91 felix.detectXercesOverride("foo.bar,baz.jim");
92 felix.detectXercesOverride("foo.bar,org.apache.xerces.util;version=\"1.0\",baz.jim");
93 felix.detectXercesOverride("foo.bar,org.apache.xerces.util;version=\"1.0\"");
94 felix.detectXercesOverride("foo.bar,repackaged.org.apache.xerces.util,bar.baz");
95
96
97 try
98 {
99 felix.detectXercesOverride("foo.bar,org.apache.xerces.util");
100 fail("Should fail validation");
101 }
102 catch (OsgiContainerException ex)
103 {
104
105 }
106
107 try
108 {
109 felix.detectXercesOverride("org.apache.xerces.util");
110 fail("Should fail validation");
111 }
112 catch (OsgiContainerException ex)
113 {
114
115 }
116
117 try
118 {
119 felix.detectXercesOverride("org.apache.xerces.util,bar.baz");
120 fail("Should fail validation");
121 }
122 catch (OsgiContainerException ex)
123 {
124
125 }
126
127 }
128
129 public void testDeleteDirectory() throws IOException
130 {
131 File dir = new File(tmpdir, "base");
132 dir.mkdir();
133 File subdir = new File(dir, "subdir");
134 subdir.mkdir();
135 File kid = File.createTempFile("foo", "bar", subdir);
136
137 FileUtils.deleteDirectory(dir);
138 assertTrue(!kid.exists());
139 assertTrue(!subdir.exists());
140 assertTrue(!dir.exists());
141 }
142
143 public void testStartStop()
144 {
145 FilenameFilter filter = new FilenameFilter()
146 {
147 public boolean accept(File file, String s)
148 {
149 return s.startsWith("felix");
150 }
151 };
152 int filesNamedFelix = tmpdir.listFiles(filter).length;
153 felix.start();
154 assertTrue(felix.isRunning());
155 assertEquals(1, felix.getBundles().length);
156 felix.stop();
157 assertEquals(filesNamedFelix, tmpdir.listFiles(filter).length);
158 }
159
160 public void testInstallBundle() throws URISyntaxException
161 {
162 felix.start();
163 assertEquals(1, felix.getBundles().length);
164 File jar = new File(getClass().getResource("/myapp-1.0.jar").toURI());
165 felix.installBundle(jar);
166 assertEquals(2, felix.getBundles().length);
167 assertThat(felix.getBundles()[1], not(isReference()));
168
169 String[] justJar = { "jar" };
170 Collection<File> cachedJars = FileUtils.listFiles(osgiPersistentCache.getOsgiBundleCache(), justJar, true);
171 assertThat(cachedJars, hasSize(1));
172 }
173
174 public void testInstallBundleAllowReference() throws URISyntaxException
175 {
176 felix.start();
177 assertEquals(1, felix.getBundles().length);
178 File jar = new File(getClass().getResource("/myapp-1.0.jar").toURI());
179 felix.installBundle(jar, true);
180 assertEquals(2, felix.getBundles().length);
181 assertThat(felix.getBundles()[1], isReference());
182
183 String[] justJar = { "jar" };
184 Collection<File> cachedJars = FileUtils.listFiles(osgiPersistentCache.getOsgiBundleCache(), justJar, true);
185 assertThat(cachedJars, empty());
186 }
187
188 public void testAllowsReferenceInstallDefaultForwardsRequest() throws URISyntaxException
189 {
190 felix.start();
191 assertEquals(1, felix.getBundles().length);
192 File jar = new File(getClass().getResource("/myapp-1.0.jar").toURI());
193 OsgiContainerManager.AllowsReferenceInstall.Default.installBundle(felix, jar, true);
194 assertEquals(2, felix.getBundles().length);
195 assertThat(felix.getBundles()[1], isReference());
196 }
197
198 public void testInstallFileBundleWithReferenceProtocolDisabled() throws URISyntaxException
199 {
200 try
201 {
202 felix.start();
203 assertEquals(1, felix.getBundles().length);
204 System.setProperty("atlassian.felix.disable.reference.protocol", "true");
205 File jar = new File(getClass().getResource("/myapp-1.0.jar").toURI());
206 felix.installBundle(jar, true);
207 assertEquals(2, felix.getBundles().length);
208 assertThat(felix.getBundles()[1], not(isReference()));
209 }
210 finally
211 {
212 System.clearProperty("atlassian.felix.disable.reference.protocol");
213 }
214 }
215
216 public void testBootDelegation() throws Exception
217 {
218
219 File pluginServer = new PluginJarBuilder("plugin")
220 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
221 "Bundle-Version: 1.0\n" +
222 "Bundle-SymbolicName: my.server\n" +
223 "Bundle-ManifestVersion: 2\n" +
224 "Export-Package: my.server\n")
225 .addJava("my.server.ServerClass", "package my.server; public class ServerClass extends junit.framework.TestCase {}")
226 .build();
227
228
229
230
231 File pluginClient = new PluginJarBuilder("plugin")
232 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
233 "Bundle-Version: 1.0\n" +
234 "Bundle-SymbolicName: my.client\n" +
235 "Bundle-ManifestVersion: 2\n" +
236 "Import-Package: my.server\n")
237 .addJava("my.client.ClientClass", "package my.client; public class ClientClass {" +
238 "public ClientClass() throws ClassNotFoundException {" +
239 "getClass().getClassLoader().loadClass(\"my.server.ServerClass\");" +
240 "}}")
241 .build();
242
243 felix.start();
244 Bundle serverBundle = felix.installBundle(pluginServer);
245 serverBundle.start();
246 Bundle clientBundle = felix.installBundle(pluginClient);
247 clientBundle.start();
248 try
249 {
250 clientBundle.loadClass("my.client.ClientClass").newInstance();
251 fail("Expected exception: NoClassDefFoundError for junit.framework.TestCase");
252 }
253 catch (NoClassDefFoundError expected)
254 {
255 }
256 felix.stop();
257
258
259 System.setProperty("atlassian.org.osgi.framework.bootdelegation", "junit.framework,junit.framework.*");
260 try
261 {
262 felix.start();
263 serverBundle = felix.installBundle(pluginServer);
264 serverBundle.start();
265 clientBundle = felix.installBundle(pluginClient);
266 clientBundle.start();
267 clientBundle.loadClass("my.client.ClientClass").newInstance();
268 felix.stop();
269 }
270 finally
271 {
272 System.clearProperty("atlassian.org.osgi.framework.bootdelegation");
273 }
274 }
275
276 public void testInstallBundleTwice() throws URISyntaxException, IOException, BundleException
277 {
278 File plugin = new PluginJarBuilder("plugin")
279 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
280 "Import-Package: javax.swing\n" +
281 "Bundle-Version: 1.0\n" +
282 "Bundle-SymbolicName: my.foo.symbolicName\n" +
283 "Bundle-ManifestVersion: 2\n")
284 .addResource("foo.txt", "foo")
285 .build();
286
287 File pluginUpdate = new PluginJarBuilder("plugin")
288 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
289 "Import-Package: javax.swing\n" +
290 "Bundle-Version: 1.0\n" +
291 "Bundle-SymbolicName: my.foo.symbolicName\n" +
292 "Bundle-ManifestVersion: 2\n")
293 .addResource("bar.txt", "bar")
294 .build();
295
296 felix.start();
297 assertEquals(1, felix.getBundles().length);
298 Bundle bundle = felix.installBundle(plugin);
299 assertEquals(2, felix.getBundles().length);
300 assertEquals("my.foo.symbolicName", bundle.getSymbolicName());
301 assertEquals("1.0", bundle.getHeaders().get(Constants.BUNDLE_VERSION));
302 assertEquals(Bundle.INSTALLED, bundle.getState());
303 assertNotNull(bundle.getResource("foo.txt"));
304 assertNull(bundle.getResource("bar.txt"));
305 bundle.start();
306 assertEquals(Bundle.ACTIVE, bundle.getState());
307 Bundle bundleUpdate = felix.installBundle(pluginUpdate);
308 assertEquals(2, felix.getBundles().length);
309 assertEquals(Bundle.INSTALLED, bundleUpdate.getState());
310 bundleUpdate.start();
311 assertEquals(Bundle.ACTIVE, bundleUpdate.getState());
312
313 assertNotNull(bundleUpdate.getResource("bar.txt"));
314 }
315
316 public void testInstallBundleTwiceDifferentSymbolicNames() throws URISyntaxException, IOException, BundleException
317 {
318 File plugin = new PluginJarBuilder("plugin")
319 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
320 "Import-Package: javax.swing\n" +
321 "Bundle-Version: 1.0\n" +
322 "Bundle-SymbolicName: my.foo\n" +
323 "Atlassian-Plugin-Key: my.foo.symbolicName\n" +
324 "Bundle-ManifestVersion: 2\n")
325 .addResource("foo.txt", "foo")
326 .build();
327
328 File pluginUpdate = new PluginJarBuilder("plugin")
329 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
330 "Import-Package: javax.swing\n" +
331 "Bundle-Version: 1.0\n" +
332 "Atlassian-Plugin-Key: my.foo.symbolicName\n" +
333 "Bundle-SymbolicName: my.bar\n" +
334 "Bundle-ManifestVersion: 2\n")
335 .addResource("bar.txt", "bar")
336 .build();
337
338 felix.start();
339 assertEquals(1, felix.getBundles().length);
340 Bundle bundle = felix.installBundle(plugin);
341 assertEquals(2, felix.getBundles().length);
342 assertEquals("1.0", bundle.getHeaders().get(Constants.BUNDLE_VERSION));
343 assertEquals(Bundle.INSTALLED, bundle.getState());
344 assertNotNull(bundle.getResource("foo.txt"));
345 assertNull(bundle.getResource("bar.txt"));
346 bundle.start();
347 assertEquals(Bundle.ACTIVE, bundle.getState());
348 Bundle bundleUpdate = felix.installBundle(pluginUpdate);
349 assertEquals(2, felix.getBundles().length);
350 assertEquals(Bundle.INSTALLED, bundleUpdate.getState());
351 bundleUpdate.start();
352 assertEquals(Bundle.ACTIVE, bundleUpdate.getState());
353
354 assertNotNull(bundleUpdate.getResource("bar.txt"));
355 }
356
357 public void testInstallFailure() throws Exception
358 {
359 File plugin = new PluginJarBuilder("plugin")
360 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
361 "Bundle-Version: 1.0\n" +
362 "Import-Package: foo.missing.package\n" +
363 "Bundle-SymbolicName: my.foo.symbolicName\n" +
364 "Bundle-ManifestVersion: 2\n" )
365 .build();
366 felix.start();
367
368 Bundle bundle = felix.installBundle(plugin);
369 try {
370 bundle.loadClass("foo.bar");
371 fail("Should have thrown exception");
372 } catch (ClassNotFoundException ex) {
373
374 }
375 }
376
377 public void testServiceTrackerIsClosed() throws URISyntaxException, IOException, BundleException
378 {
379 felix.start();
380
381 ServiceTracker tracker = felix.getServiceTracker(PackageAdmin.class.getCanonicalName());
382 Object[] trackedServices = tracker.getServices();
383 assertNotNull("Unable to perform the test: no service to track", trackedServices);
384
385 tracker.close();
386
387 trackedServices = tracker.getServices();
388 assertNull(trackedServices);
389 }
390
391 public void testServiceTrackerActuallyTracksStuff() throws Exception
392 {
393 startFelixWithListAsOSGiService();
394
395 ServiceTracker tracker = felix.getServiceTracker(List.class.getName());
396 Object[] trackedServices = tracker.getServices();
397 assertEquals(1, trackedServices.length);
398 assertEquals(ImmutableList.of("blah"), trackedServices[0]);
399 }
400
401 public void testServiceTrackerCutomizerIsInPlace() throws Exception
402 {
403 startFelixWithListAsOSGiService();
404 final CountDownLatch latch = new CountDownLatch(1);
405
406 felix.getServiceTracker(List.class.getName(), new ServiceTrackerCustomizer()
407 {
408 public Object addingService(ServiceReference reference)
409 {
410 latch.countDown();
411 return reference;
412 }
413 public void modifiedService(ServiceReference reference, Object service)
414 {
415 }
416 public void removedService(ServiceReference reference, Object service)
417 {
418 }
419 });
420 assertTrue(latch.await(10, TimeUnit.SECONDS));
421 }
422
423 private void startFelixWithListAsOSGiService() throws Exception
424 {
425 File plugin = new PluginJarBuilder("plugin")
426 .addResource("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" +
427 "Bundle-Version: 1.0\n" +
428 "Atlassian-Plugin-Key: my.foo.symbolicName\n" +
429 "Bundle-Name: my.bar\n" +
430 "Bundle-SymbolicName: my.bar\n" +
431 "Bundle-ManifestVersion: 2\n" +
432 "Bundle-Activator: my.MyActivator\n" +
433 "Import-Package: org.osgi.framework\n")
434 .addJava("my.MyActivator", "package my;" +
435 "import org.osgi.framework.ServiceRegistration;\n" +
436 "import org.osgi.framework.BundleActivator;\n" +
437 "import org.osgi.framework.BundleContext;\n" +
438 "import java.util.*;\n" +
439 "public class MyActivator implements BundleActivator {\n" +
440 " private ServiceRegistration registration;\n" +
441 " public void start(BundleContext context) throws Exception\n" +
442 " {\n" +
443 " context.registerService(List.class.getName(), Arrays.asList(new Object[]{ \"blah\" }), new Properties());\n" +
444 " }\n" +
445 " public void stop(BundleContext context) throws Exception {}\n" +
446 "}")
447 .build();
448 felix.start();
449 Bundle bundle = felix.installBundle(plugin);
450 bundle.start();
451 }
452
453 public void testRuntimeEnvironment()
454 {
455 try
456 {
457 String formatStr = String.format("java.version=%s,plugin.enable.timeout=%%d", System.getProperty("java.version"));
458
459 assertEquals(String.format(formatStr, setPluginTimeout(3883)), felix.getRuntimeEnvironment());
460 }
461 finally
462 {
463 setPluginTimeout(-1);
464 }
465 }
466
467 public void testRuntimeEnvironmentIntegration() throws Exception
468 {
469 try
470 {
471 int timeout = 678;
472 setPluginTimeout(timeout);
473
474 startFelixWithListAsOSGiService();
475
476 File versionFile = new File(new File(tmpdir, "transformed-plugins"), "cache.key");
477 assertTrue(versionFile.exists());
478 String txt = FileUtils.readFileToString(versionFile);
479
480 ExportsBuilder eBuilder = new ExportsBuilder();
481 String systemExports = eBuilder.getExports(new ArrayList<HostComponentRegistration>(), new DefaultPackageScannerConfiguration());
482
483
484 String expectedKey = String.format("java.version=%s,plugin.enable.timeout=%d,%s", System.getProperty("java.version"), timeout, systemExports);
485 assertEquals(expectedKey.hashCode(), Integer.parseInt(txt));
486 }
487 finally
488 {
489 setPluginTimeout(-1);
490 }
491 }
492
493 private int setPluginTimeout(int timeout)
494 {
495 if (timeout <= 0)
496 {
497 System.clearProperty(PluginUtils.ATLASSIAN_PLUGINS_ENABLE_WAIT);
498 return 60;
499 }
500 else
501 {
502 System.setProperty(PluginUtils.ATLASSIAN_PLUGINS_ENABLE_WAIT, String.valueOf(timeout));
503 return timeout;
504 }
505 }
506
507
508
509
510
511
512 private static Matcher<Bundle> isReference()
513 {
514 return new TypeSafeMatcher<Bundle>()
515 {
516 public boolean matchesSafely(Bundle bundle)
517 {
518 return bundle.getLocation().startsWith(BundleArchive.REFERENCE_PROTOCOL);
519 }
520
521 public void describeTo(Description description)
522 {
523 description.appendText("bundle with reference location");
524 }
525 };
526 }
527 }