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