View Javadoc

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(); // prevent export cache from being reused across tests
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             // should fail
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             // should fail
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             // should fail
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         // There should a single jar in the felix cache now
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         // There should be no jars in the felix cache
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         // Server class extends JUnit TestCase class, which is not available to the bundle
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         // Client is necessary to load the server class in a Felix ContentClassLoader, to avoid the hack in Felix's
244         // R4SearchPolicyCore (approx. line 591) which will use parent delegation if a class cannot be found
245         // and the calling classloader is not a ContentClassLoader.
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         // This system property exposes the JUnit TestCase class from the parent classloader to the bundle
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         //assertNull(bundleUpdate.getResource("foo.txt"));
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         //assertNull(bundleUpdate.getResource("foo.txt"));
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             // no worries
392         }
393     }
394 
395     public void testServiceTrackerIsClosed() throws URISyntaxException, IOException, BundleException
396     {
397         felix.start();
398         // PackageAdmin is an example of service which is loaded by default in Felix
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             // cache key should include the current JVM version
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         // Check that nothing.zip, at frameworkBundlesUrl is unpacked when the framework is started
523         // Should start empty or the test is broken
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         // Even after start, the framework bundle cache should be empty
551         assertThat(osgiPersistentCache.getFrameworkBundleCache().listFiles(), emptyArray());
552         // And we should have found the bundle we put in the provided directory
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      * Matches a Bundle whose location indicates that it is a reference bundle.
572      *
573      * @return a matcher for bundles whose location is a reference: location.
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      * Matches a Bundle whose symbolic name is as given.
593      *
594      * @param symbolicName the expected symbolic name for the bundle.
595      * @return a matcher for bundles whose symbolic name is {@code symbolicName}.
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 }