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