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.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(); // prevent export cache from being reused across tests
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             // should fail
105         }
106 
107         try
108         {
109             felix.detectXercesOverride("org.apache.xerces.util");
110             fail("Should fail validation");
111         }
112         catch (OsgiContainerException ex)
113         {
114             // should fail
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             // should fail
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         // There should a single jar in the felix cache now
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         // There should be no jars in the felix cache
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         // Server class extends JUnit TestCase class, which is not available to the bundle
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         // Client is necessary to load the server class in a Felix ContentClassLoader, to avoid the hack in Felix's
229         // R4SearchPolicyCore (approx. line 591) which will use parent delegation if a class cannot be found
230         // and the calling classloader is not a ContentClassLoader.
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         // This system property exposes the JUnit TestCase class from the parent classloader to the bundle
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         //assertNull(bundleUpdate.getResource("foo.txt"));
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         //assertNull(bundleUpdate.getResource("foo.txt"));
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             // no worries
374         }
375     }
376 
377     public void testServiceTrackerIsClosed() throws URISyntaxException, IOException, BundleException
378     {
379         felix.start();
380         // PackageAdmin is an example of service which is loaded by default in Felix
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             // cache key should include the current JVM version
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      * Matches a Bundle whose location indicates that it is a reference bundle.
509      *
510      * @return a matcher for bundles whose location is a reference: location.
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 }