View Javadoc

1   package com.atlassian.plugin.osgi.container.felix;
2   
3   import com.atlassian.plugin.event.PluginEventListener;
4   import com.atlassian.plugin.event.PluginEventManager;
5   import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
6   import com.atlassian.plugin.event.events.PluginFrameworkStartingEvent;
7   import com.atlassian.plugin.osgi.container.OsgiContainerException;
8   import com.atlassian.plugin.osgi.container.OsgiContainerManager;
9   import com.atlassian.plugin.osgi.container.PackageScannerConfiguration;
10  import com.atlassian.plugin.osgi.hostcomponents.HostComponentProvider;
11  import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
12  import com.atlassian.plugin.osgi.hostcomponents.impl.DefaultComponentRegistrar;
13  import com.atlassian.plugin.util.ClassLoaderUtils;
14  
15  import org.apache.commons.io.FileUtils;
16  import org.apache.commons.lang.Validate;
17  import org.apache.commons.logging.Log;
18  import org.apache.commons.logging.LogFactory;
19  import org.apache.felix.framework.Felix;
20  import org.apache.felix.framework.Logger;
21  import org.apache.felix.framework.cache.BundleCache;
22  import org.apache.felix.framework.util.FelixConstants;
23  import org.apache.felix.framework.util.StringMap;
24  import org.osgi.framework.Bundle;
25  import org.osgi.framework.BundleActivator;
26  import org.osgi.framework.BundleContext;
27  import org.osgi.framework.BundleEvent;
28  import org.osgi.framework.BundleException;
29  import org.osgi.framework.BundleListener;
30  import org.osgi.framework.Constants;
31  import org.osgi.framework.FrameworkEvent;
32  import org.osgi.framework.FrameworkListener;
33  import org.osgi.framework.ServiceReference;
34  import org.osgi.framework.ServiceRegistration;
35  import org.osgi.service.packageadmin.PackageAdmin;
36  import org.osgi.util.tracker.ServiceTracker;
37  
38  import java.io.File;
39  import java.io.FilenameFilter;
40  import java.io.IOException;
41  import java.net.URL;
42  import java.util.ArrayList;
43  import java.util.Collections;
44  import java.util.HashSet;
45  import java.util.List;
46  import java.util.Set;
47  import java.util.concurrent.ThreadFactory;
48  import java.util.concurrent.CountDownLatch;
49  import java.util.concurrent.TimeUnit;
50  import java.util.jar.JarFile;
51  
52  /**
53   * Felix implementation of the OSGi container manager
54   */
55  public class FelixOsgiContainerManager implements OsgiContainerManager
56  {
57      public static final String OSGI_FRAMEWORK_BUNDLES_ZIP = "osgi-framework-bundles.zip";
58  
59      private static final Log log = LogFactory.getLog(FelixOsgiContainerManager.class);
60      private static final String OSGI_BOOTDELEGATION = "org.osgi.framework.bootdelegation";
61      private static final String ATLASSIAN_PREFIX = "atlassian.";
62  
63      private final File cacheDirectory;
64      private final URL frameworkBundlesUrl;
65      private final PackageScannerConfiguration packageScannerConfig;
66      private final File frameworkBundlesDir;
67      private final HostComponentProvider hostComponentProvider;
68      private final Set<ServiceTracker> trackers;
69      private final ExportsBuilder exportsBuilder;
70      private final ThreadFactory threadFactory = new ThreadFactory()
71      {
72          public Thread newThread(final Runnable r)
73          {
74              final Thread thread = new Thread(r, "Felix:Startup");
75              thread.setDaemon(true);
76              return thread;
77          }
78      };
79  
80      private BundleRegistration registration = null;
81      private Felix felix = null;
82      private boolean felixRunning = false;
83      private boolean disableMultipleBundleVersions = true;
84      private Logger felixLogger;
85  
86      /**
87       * Constructs the container manager using the framework bundles zip file located in this library
88       * @param frameworkBundlesDir The directory to unzip the framework bundles into.
89       * @param packageScannerConfig The configuration for package scanning
90       * @param provider The host component provider.  May be null.
91       * @param eventManager The plugin event manager to register for init and shutdown events
92       * @deprecated Since 2.2.0, use
93       *   {@link #FelixOsgiContainerManager(File,PackageScannerConfiguration,HostComponentProvider,PluginEventManager,File)} instead
94       */
95      @Deprecated
96      public FelixOsgiContainerManager(final File frameworkBundlesDir, final PackageScannerConfiguration packageScannerConfig, final HostComponentProvider provider, final PluginEventManager eventManager)
97      {
98          this(ClassLoaderUtils.getResource(OSGI_FRAMEWORK_BUNDLES_ZIP, FelixOsgiContainerManager.class), frameworkBundlesDir, packageScannerConfig,
99              provider, eventManager);
100     }
101 
102     /**
103      * Constructs the container manager
104      * @param frameworkBundlesZip The location of the zip file containing framework bundles
105      * @param frameworkBundlesDir The directory to unzip the framework bundles into.
106      * @param packageScannerConfig The configuration for package scanning
107      * @param provider The host component provider.  May be null.
108      * @param eventManager The plugin event manager to register for init and shutdown events
109      * @deprecated Since 2.2.0, use
110      *   {@link #FelixOsgiContainerManager(URL, File,PackageScannerConfiguration,HostComponentProvider,PluginEventManager,File)} instead
111      */
112     @Deprecated
113     public FelixOsgiContainerManager(final URL frameworkBundlesZip, final File frameworkBundlesDir, final PackageScannerConfiguration packageScannerConfig, final HostComponentProvider provider, final PluginEventManager eventManager)
114     {
115         this(frameworkBundlesZip, frameworkBundlesDir, packageScannerConfig, provider, eventManager, new File(frameworkBundlesDir.getParentFile(),
116             "osgi-cache"));
117     }
118 
119     /**
120      * Constructs the container manager using the framework bundles zip file located in this library
121      * @param frameworkBundlesDir The directory to unzip the framework bundles into.
122      * @param packageScannerConfig The configuration for package scanning
123      * @param provider The host component provider.  May be null.
124      * @param eventManager The plugin event manager to register for init and shutdown events
125      * @param cacheDir The directory to use for the felix cache
126      *
127      * @since 2.2.0
128      */
129     public FelixOsgiContainerManager(final File frameworkBundlesDir, final PackageScannerConfiguration packageScannerConfig, final HostComponentProvider provider, final PluginEventManager eventManager, final File cacheDir)
130     {
131         this(ClassLoaderUtils.getResource(OSGI_FRAMEWORK_BUNDLES_ZIP, FelixOsgiContainerManager.class), frameworkBundlesDir, packageScannerConfig,
132             provider, eventManager, cacheDir);
133     }
134 
135     /**
136      * Constructs the container manager
137      * @param frameworkBundlesZip The location of the zip file containing framework bundles
138      * @param frameworkBundlesDir The directory to unzip the framework bundles into.
139      * @param packageScannerConfig The configuration for package scanning
140      * @param provider The host component provider.  May be null.
141      * @param eventManager The plugin event manager to register for init and shutdown events
142      * @param cacheDir The directory to use for the felix cache
143      *
144      * @since 2.2.0
145      * @throws com.atlassian.plugin.osgi.container.OsgiContainerException If the host version isn't supplied and the
146      *  cache directory cannot be cleaned.
147      */
148     public FelixOsgiContainerManager(final URL frameworkBundlesZip, final File frameworkBundlesDir,
149                                      final PackageScannerConfiguration packageScannerConfig,
150                                      final HostComponentProvider provider, final PluginEventManager eventManager,
151                                      final File cacheDir) throws OsgiContainerException
152     {
153         Validate.notNull(frameworkBundlesZip, "The framework bundles zip is required");
154         Validate.notNull(frameworkBundlesDir, "The framework bundles directory must not be null");
155         Validate.notNull(packageScannerConfig, "The package scanner configuration must not be null");
156         Validate.notNull(eventManager, "The plugin event manager is required");
157 
158         frameworkBundlesUrl = frameworkBundlesZip;
159         this.packageScannerConfig = packageScannerConfig;
160         this.frameworkBundlesDir = frameworkBundlesDir;
161         hostComponentProvider = provider;
162         trackers = Collections.synchronizedSet(new HashSet<ServiceTracker>());
163         eventManager.register(this);
164         felixLogger = new FelixLoggerBridge(log);
165         exportsBuilder = new ExportsBuilder();
166         cacheDirectory = cacheDir;
167         if (!cacheDir.exists())
168         {
169             cacheDir.mkdir();
170         }
171         else //if (packageScannerConfig.getCurrentHostVersion() == null)
172         {
173             try
174             {
175                 FileUtils.cleanDirectory(cacheDir);
176             }
177             catch (final IOException e)
178             {
179                 throw new OsgiContainerException("Unable to clean the cache directory: " + cacheDir, e);
180             }
181         }
182         log.debug("Using cache directory :" + cacheDir.getAbsolutePath());
183     }
184 
185     public void setFelixLogger(final Logger logger)
186     {
187         felixLogger = logger;
188     }
189 
190     public void setDisableMultipleBundleVersions(final boolean val)
191     {
192         disableMultipleBundleVersions = val;
193     }
194 
195     @PluginEventListener
196     public void onStart(final PluginFrameworkStartingEvent event)
197     {
198         start();
199     }
200 
201     @PluginEventListener
202     public void onShutdown(final PluginFrameworkShutdownEvent event)
203     {
204         stop();
205     }
206 
207     public void start() throws OsgiContainerException
208     {
209         if (isRunning())
210         {
211             return;
212         }
213 
214         detectIncorrectOsgiVersion();
215 
216         final DefaultComponentRegistrar registrar = collectHostComponents(hostComponentProvider);
217         // Create a case-insensitive configuration property map.
218         final StringMap configMap = new StringMap(false);
219         // Configure the Felix instance to be embedded.
220         configMap.put(FelixConstants.EMBEDDED_EXECUTION_PROP, "true");
221         // Add the bundle provided service interface package and the core OSGi
222         // packages to be exported from the class path via the system bundle.
223         configMap.put(Constants.FRAMEWORK_SYSTEMPACKAGES, exportsBuilder.determineExports(registrar.getRegistry(), packageScannerConfig,
224             cacheDirectory));
225 
226         // Explicitly specify the directory to use for caching bundles.
227         configMap.put(BundleCache.CACHE_PROFILE_DIR_PROP, cacheDirectory.getAbsolutePath());
228 
229         configMap.put(FelixConstants.LOG_LEVEL_PROP, String.valueOf(felixLogger.getLogLevel()));
230         String bootDelegation = getAtlassianSpecificOsgiSystemProperty(OSGI_BOOTDELEGATION);
231         if ((bootDelegation == null) || (bootDelegation.trim().length() == 0))
232         {
233             bootDelegation = "weblogic.*,META-INF.services,com.yourkit.*,com.jprofiler.*,org.apache.xerces.*";
234         }
235 
236         configMap.put(Constants.FRAMEWORK_BOOTDELEGATION, bootDelegation);
237         if (log.isDebugEnabled())
238         {
239             log.debug("Felix configuration: " + configMap);
240         }
241 
242         try
243         {
244             // Create host activator;
245             registration = new BundleRegistration(frameworkBundlesUrl, frameworkBundlesDir, registrar);
246             final List<BundleActivator> list = new ArrayList<BundleActivator>();
247             list.add(registration);
248 
249             // Now create an instance of the framework with
250             // our configuration properties and activator.
251             felix = new Felix(felixLogger, configMap, list);
252 
253             // Now start Felix instance.  Starting in a different thread to explicity set daemon status
254             final Runnable start = new Runnable()
255             {
256                 public void run()
257                 {
258                     try
259                     {
260                         felix.start();
261                         felixRunning = true;
262                     }
263                     catch (final BundleException e)
264                     {
265                         throw new OsgiContainerException("Unable to start felix", e);
266                     }
267                 }
268             };
269             final Thread t = threadFactory.newThread(start);
270             t.start();
271 
272             // Give it 10 seconds
273             t.join(10 * 60 * 1000);
274         }
275         catch (final Exception ex)
276         {
277             throw new OsgiContainerException("Unable to start OSGi container", ex);
278         }
279     }
280 
281     /**
282      * Detects incorrect configuration of WebSphere 6.1 that leaks OSGi 4.0 jars into the application
283      */
284     private void detectIncorrectOsgiVersion()
285     {
286         try
287         {
288             Bundle.class.getMethod("getBundleContext");
289         }
290         catch (final NoSuchMethodException e)
291         {
292             throw new OsgiContainerException(
293                 "Detected older version (4.0 or earlier) of OSGi.  If using WebSphere " + "6.1, please enable application-first (parent-last) classloading and the 'Single classloader for " + "application' WAR classloader policy.");
294         }
295     }
296 
297     public void stop() throws OsgiContainerException
298     {
299         if (felixRunning)
300         {
301             for (final ServiceTracker tracker : new HashSet<ServiceTracker>(trackers))
302             {
303                 tracker.close();
304             }
305             felix.stopAndWait();
306         }
307 
308         felixRunning = false;
309         felix = null;
310     }
311 
312     public Bundle[] getBundles()
313     {
314         if (isRunning())
315         {
316             return registration.getBundles();
317         }
318         else
319         {
320             throw new IllegalStateException(
321                 "Cannot retrieve the bundles if the Felix container isn't running. Check earlier in the logs for the possible cause as to why Felix didn't start correctly.");
322         }
323     }
324 
325     public ServiceReference[] getRegisteredServices()
326     {
327         return felix.getRegisteredServices();
328     }
329 
330     public ServiceTracker getServiceTracker(final String cls)
331     {
332         if (!isRunning())
333         {
334             throw new IllegalStateException("Unable to create a tracker when osgi is not running");
335         }
336 
337         final ServiceTracker tracker = registration.getServiceTracker(cls, trackers);
338         tracker.open();
339         trackers.add(tracker);
340         return tracker;
341     }
342 
343     public Bundle installBundle(final File file) throws OsgiContainerException
344     {
345         try
346         {
347             return registration.install(file, disableMultipleBundleVersions);
348         }
349         catch (final BundleException e)
350         {
351             throw new OsgiContainerException("Unable to install bundle", e);
352         }
353     }
354 
355     DefaultComponentRegistrar collectHostComponents(final HostComponentProvider provider)
356     {
357         final DefaultComponentRegistrar registrar = new DefaultComponentRegistrar();
358         if (provider != null)
359         {
360             provider.provide(registrar);
361         }
362         return registrar;
363     }
364 
365     public boolean isRunning()
366     {
367         return felixRunning;
368     }
369 
370     public List<HostComponentRegistration> getHostComponentRegistrations()
371     {
372         return registration.getHostComponentRegistrations();
373     }
374 
375     private String getAtlassianSpecificOsgiSystemProperty(final String originalSystemProperty)
376     {
377         return System.getProperty(ATLASSIAN_PREFIX + originalSystemProperty);
378     }
379 
380     /**
381      * Manages framework-level framework bundles and host components registration, and individual plugin bundle
382      * installation and removal.
383      */
384     static class BundleRegistration implements BundleActivator, BundleListener
385     {
386         private BundleContext bundleContext;
387         private final DefaultComponentRegistrar registrar;
388         private List<ServiceRegistration> hostServicesReferences;
389         private List<HostComponentRegistration> hostComponentRegistrations;
390         private final URL frameworkBundlesUrl;
391         private final File frameworkBundlesDir;
392         private PackageAdmin packageAdmin;
393 
394         public BundleRegistration(final URL frameworkBundlesUrl, final File frameworkBundlesDir, final DefaultComponentRegistrar registrar)
395         {
396             this.registrar = registrar;
397             this.frameworkBundlesUrl = frameworkBundlesUrl;
398             this.frameworkBundlesDir = frameworkBundlesDir;
399         }
400 
401         public void start(final BundleContext context) throws Exception
402         {
403             bundleContext = context;
404             final ServiceReference ref = context.getServiceReference(org.osgi.service.packageadmin.PackageAdmin.class.getName());
405             packageAdmin = (PackageAdmin) context.getService(ref);
406 
407             context.addBundleListener(this);
408 
409             loadHostComponents(registrar);
410             extractAndInstallFrameworkBundles();
411             context.addFrameworkListener(new FrameworkListener()
412             {
413                 public void frameworkEvent(final FrameworkEvent event)
414                 {
415                     String bundleBits = "";
416                     if (event.getBundle() != null)
417                     {
418                         bundleBits = " in bundle " + event.getBundle().getSymbolicName();
419                     }
420                     switch (event.getType())
421                     {
422                         case FrameworkEvent.ERROR:
423                             log.error("Framework error" + bundleBits, event.getThrowable());
424                             break;
425                         case FrameworkEvent.WARNING:
426                             log.warn("Framework warning" + bundleBits, event.getThrowable());
427                             break;
428                         case FrameworkEvent.INFO:
429                             log.info("Framework info" + bundleBits, event.getThrowable());
430                             break;
431                     }
432                 }
433             });
434         }
435 
436         public void stop(final BundleContext ctx) throws Exception
437         {}
438 
439         public void bundleChanged(final BundleEvent evt)
440         {
441             switch (evt.getType())
442             {
443                 case BundleEvent.INSTALLED:
444                     log.info("Installed bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
445                     break;
446                 case BundleEvent.RESOLVED:
447                     log.info("Resolved bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
448                     break;
449                 case BundleEvent.UNRESOLVED:
450                     log.info("Unresolved bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
451                     break;
452                 case BundleEvent.STARTED:
453                     log.info("Started bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
454                     break;
455                 case BundleEvent.STOPPED:
456                     log.info("Stopped bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
457                     break;
458                 case BundleEvent.UNINSTALLED:
459                     log.info("Uninstalled bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
460                     break;
461             }
462         }
463 
464         public Bundle install(final File path, final boolean uninstallOtherVersions) throws BundleException
465         {
466             boolean bundleUninstalled = false;
467             if (uninstallOtherVersions)
468             {
469                 try
470                 {
471                     final JarFile jar = new JarFile(path);
472                     final String name = jar.getManifest().getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
473                     for (final Bundle oldBundle : bundleContext.getBundles())
474                     {
475                         if (name.equals(oldBundle.getSymbolicName()))
476                         {
477                             log.info("Uninstalling existing version " + oldBundle.getHeaders().get(Constants.BUNDLE_VERSION));
478                             oldBundle.uninstall();
479                             bundleUninstalled = true;
480                         }
481                     }
482                 }
483                 catch (final IOException e)
484                 {
485                     throw new BundleException("Invalid bundle format", e);
486                 }
487             }
488             final Bundle bundle = bundleContext.installBundle(path.toURI().toString());
489             if (bundleUninstalled)
490             {
491                 refreshPackages();
492             }
493             return bundle;
494         }
495 
496         public Bundle[] getBundles()
497         {
498             return bundleContext.getBundles();
499         }
500 
501         public ServiceTracker getServiceTracker(final String clazz, final Set<ServiceTracker> trackedTrackers)
502         {
503             return new ServiceTracker(bundleContext, clazz, null)
504             {
505                 @Override
506                 public void close()
507                 {
508                     trackedTrackers.remove(this);
509                 }
510             };
511         }
512 
513         public List<HostComponentRegistration> getHostComponentRegistrations()
514         {
515             return hostComponentRegistrations;
516         }
517 
518         private void loadHostComponents(final DefaultComponentRegistrar registrar)
519         {
520             // Unregister any existing host components
521             if (hostServicesReferences != null)
522             {
523                 for (final ServiceRegistration reg : hostServicesReferences)
524                 {
525                     reg.unregister();
526                 }
527             }
528 
529             // Register host components as OSGi services
530             hostServicesReferences = registrar.writeRegistry(bundleContext);
531             hostComponentRegistrations = registrar.getRegistry();
532         }
533 
534         private void extractAndInstallFrameworkBundles() throws BundleException
535         {
536             final List<Bundle> bundles = new ArrayList<Bundle>();
537             com.atlassian.plugin.util.FileUtils.conditionallyExtractZipFile(frameworkBundlesUrl, frameworkBundlesDir);
538             for (final File bundleFile : frameworkBundlesDir.listFiles(new FilenameFilter()
539                 {
540                     public boolean accept(final File file, final String s)
541                     {
542                         return s.endsWith(".jar");
543                     }
544                 }))
545             {
546                 bundles.add(install(bundleFile, false));
547             }
548 
549             for (final Bundle bundle : bundles)
550             {
551                 bundle.start();
552             }
553         }
554 
555         private void refreshPackages()
556         {
557             final CountDownLatch latch = new CountDownLatch(1);
558             FrameworkListener refreshListener = new FrameworkListener()
559             {
560 
561                 public void frameworkEvent(FrameworkEvent event)
562                 {
563                     if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED)
564                     {
565                         log.info("Packages refreshed");
566                         latch.countDown();
567                     }
568                 }
569             };
570             bundleContext.addFrameworkListener(refreshListener);
571             bundleContext.addBundleListener(new BundleListener()
572             {
573                 public void bundleChanged(BundleEvent bundleEvent)
574                 {
575                     //To change body of implemented methods use File | Settings | File Templates.
576                 }
577             });
578 
579             packageAdmin.refreshPackages(null);
580             boolean refreshed = false;
581             try
582             {
583                 refreshed = latch.await(10, TimeUnit.SECONDS);
584             }
585             catch (InterruptedException e)
586             {
587                 // ignore
588             }
589             if (!refreshed)
590             {
591                 log.warn("Timeout exceeded waiting for package refresh");
592             }
593             bundleContext.removeFrameworkListener(refreshListener);
594         }
595     }
596 
597 }