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