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