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.URL;
7   import java.util.ArrayList;
8   import java.util.Collection;
9   import java.util.Collections;
10  import java.util.HashSet;
11  import java.util.List;
12  import java.util.concurrent.CountDownLatch;
13  import java.util.concurrent.ThreadFactory;
14  import java.util.concurrent.TimeUnit;
15  import java.util.jar.JarFile;
16  
17  import com.atlassian.plugin.event.PluginEventListener;
18  import com.atlassian.plugin.event.PluginEventManager;
19  import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
20  import com.atlassian.plugin.event.events.PluginFrameworkStartingEvent;
21  import com.atlassian.plugin.event.events.PluginFrameworkWarmRestartingEvent;
22  import com.atlassian.plugin.event.events.PluginUninstalledEvent;
23  import com.atlassian.plugin.event.events.PluginUpgradedEvent;
24  import com.atlassian.plugin.osgi.container.OsgiContainerException;
25  import com.atlassian.plugin.osgi.container.OsgiContainerManager;
26  import com.atlassian.plugin.osgi.container.OsgiContainerStartedEvent;
27  import com.atlassian.plugin.osgi.container.OsgiContainerStoppedEvent;
28  import com.atlassian.plugin.osgi.container.OsgiPersistentCache;
29  import com.atlassian.plugin.osgi.container.PackageScannerConfiguration;
30  import com.atlassian.plugin.osgi.container.impl.DefaultOsgiPersistentCache;
31  import com.atlassian.plugin.osgi.hostcomponents.HostComponentProvider;
32  import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
33  import com.atlassian.plugin.osgi.hostcomponents.impl.DefaultComponentRegistrar;
34  import com.atlassian.plugin.osgi.util.OsgiHeaderUtil;
35  import com.atlassian.plugin.util.ClassLoaderUtils;
36  import com.atlassian.plugin.util.ContextClassLoaderSwitchingUtil;
37  
38  import com.atlassian.plugin.util.PluginUtils;
39  import com.google.common.annotations.VisibleForTesting;
40  import org.apache.commons.lang.StringUtils;
41  import org.apache.commons.lang.Validate;
42  import org.apache.felix.framework.Felix;
43  import org.apache.felix.framework.Logger;
44  import org.apache.felix.framework.cache.BundleCache;
45  import org.apache.felix.framework.util.FelixConstants;
46  import org.apache.felix.framework.util.StringMap;
47  import org.osgi.framework.Bundle;
48  import org.osgi.framework.BundleActivator;
49  import org.osgi.framework.BundleContext;
50  import org.osgi.framework.BundleEvent;
51  import org.osgi.framework.BundleException;
52  import org.osgi.framework.BundleListener;
53  import org.osgi.framework.Constants;
54  import org.osgi.framework.FrameworkEvent;
55  import org.osgi.framework.FrameworkListener;
56  import org.osgi.framework.ServiceReference;
57  import org.osgi.framework.ServiceRegistration;
58  import org.osgi.service.packageadmin.PackageAdmin;
59  import org.osgi.util.tracker.ServiceTracker;
60  import org.osgi.util.tracker.ServiceTrackerCustomizer;
61  import org.slf4j.LoggerFactory;
62  
63  /**
64   * Felix implementation of the OSGi container manager
65   */
66  public final class FelixOsgiContainerManager implements OsgiContainerManager
67  {
68      public static final String OSGI_FRAMEWORK_BUNDLES_ZIP = "osgi-framework-bundles.zip";
69      public static final int REFRESH_TIMEOUT = 10;
70  
71      private static final org.slf4j.Logger log = LoggerFactory.getLogger(FelixOsgiContainerManager.class);
72      private static final String OSGI_BOOTDELEGATION = "org.osgi.framework.bootdelegation";
73      private static final String ATLASSIAN_PREFIX = "atlassian.";
74  
75      private final OsgiPersistentCache persistentCache;
76      private final URL frameworkBundlesUrl;
77      private final PackageScannerConfiguration packageScannerConfig;
78      private final HostComponentProvider hostComponentProvider;
79      private final List<ServiceTracker> trackers;
80      private final ExportsBuilder exportsBuilder;
81      private final ThreadFactory threadFactory = new ThreadFactory()
82      {
83          public Thread newThread(final Runnable r)
84          {
85              final Thread thread = new Thread(r, "Felix:Startup");
86              thread.setDaemon(true);
87              return thread;
88          }
89      };
90  
91      private BundleRegistration registration = null;
92      private Felix felix = null;
93      private boolean felixRunning = false;
94      private boolean disableMultipleBundleVersions = true;
95      private Logger felixLogger;
96      private final PluginEventManager pluginEventManager;
97  
98      /**
99       * Constructs the container manager using the framework bundles zip file located in this library
100      * @param frameworkBundlesDir The directory to unzip the framework bundles into.
101      * @param packageScannerConfig The configuration for package scanning
102      * @param provider The host component provider.  May be null.
103      * @param eventManager The plugin event manager to register for init and shutdown events
104      * @deprecated Since 2.2.0, use
105      *   {@link #FelixOsgiContainerManager(OsgiPersistentCache,PackageScannerConfiguration,HostComponentProvider,PluginEventManager)} instead
106      */
107     @Deprecated
108     public FelixOsgiContainerManager(final File frameworkBundlesDir, final PackageScannerConfiguration packageScannerConfig, final HostComponentProvider provider, final PluginEventManager eventManager)
109     {
110         this(ClassLoaderUtils.getResource(OSGI_FRAMEWORK_BUNDLES_ZIP, FelixOsgiContainerManager.class), frameworkBundlesDir, packageScannerConfig,
111             provider, eventManager);
112     }
113 
114     /**
115      * Constructs the container manager
116      * @param frameworkBundlesZip The location of the zip file containing framework bundles
117      * @param frameworkBundlesDir The directory to unzip the framework bundles into.
118      * @param packageScannerConfig The configuration for package scanning
119      * @param provider The host component provider.  May be null.
120      * @param eventManager The plugin event manager to register for init and shutdown events
121      * @deprecated Since 2.2.0, use
122      *   {@link #FelixOsgiContainerManager(URL, OsgiPersistentCache,PackageScannerConfiguration,HostComponentProvider,PluginEventManager)} instead
123      */
124     @Deprecated
125     public FelixOsgiContainerManager(final URL frameworkBundlesZip, final File frameworkBundlesDir, final PackageScannerConfiguration packageScannerConfig, final HostComponentProvider provider, final PluginEventManager eventManager)
126     {
127         this(frameworkBundlesZip, new DefaultOsgiPersistentCache(new File(frameworkBundlesDir.getParentFile(),
128             "osgi-cache")), packageScannerConfig, provider, eventManager);
129     }
130 
131     /**
132      * Constructs the container manager using the framework bundles zip file located in this library
133      * @param persistentCache The persistent cache configuration.
134      * @param packageScannerConfig The configuration for package scanning
135      * @param provider The host component provider.  May be null.
136      * @param eventManager The plugin event manager to register for init and shutdown events
137      *
138      * @since 2.2.0
139      */
140     public FelixOsgiContainerManager(final OsgiPersistentCache persistentCache, final PackageScannerConfiguration packageScannerConfig, final HostComponentProvider provider, final PluginEventManager eventManager)
141     {
142         this(ClassLoaderUtils.getResource(OSGI_FRAMEWORK_BUNDLES_ZIP, FelixOsgiContainerManager.class), persistentCache, packageScannerConfig,
143             provider, eventManager);
144     }
145 
146     /**
147      * Constructs the container manager
148      * @param frameworkBundlesZip The location of the zip file containing framework bundles
149      * @param persistentCache The persistent cache to use for the framework and framework bundles
150      * @param packageScannerConfig The configuration for package scanning
151      * @param provider The host component provider.  May be null.
152      * @param eventManager The plugin event manager to register for init and shutdown events
153      *
154      * @since 2.2.0
155      * @throws com.atlassian.plugin.osgi.container.OsgiContainerException If the host version isn't supplied and the
156      *  cache directory cannot be cleaned.
157      */
158     public FelixOsgiContainerManager(final URL frameworkBundlesZip, OsgiPersistentCache persistentCache,
159                                      final PackageScannerConfiguration packageScannerConfig,
160                                      final HostComponentProvider provider, final PluginEventManager eventManager)
161                                      throws OsgiContainerException
162     {
163         Validate.notNull(frameworkBundlesZip, "The framework bundles zip is required");
164         Validate.notNull(persistentCache, "The framework bundles directory must not be null");
165         Validate.notNull(packageScannerConfig, "The package scanner configuration must not be null");
166         Validate.notNull(eventManager, "The plugin event manager is required");
167 
168         frameworkBundlesUrl = frameworkBundlesZip;
169         this.packageScannerConfig = packageScannerConfig;
170         this.persistentCache = persistentCache;
171         hostComponentProvider = provider;
172         trackers = Collections.synchronizedList(new ArrayList<ServiceTracker>());
173         this.pluginEventManager = eventManager;
174         eventManager.register(this);
175         felixLogger = new FelixLoggerBridge(log);
176         exportsBuilder = new ExportsBuilder();
177     }
178 
179     public void setFelixLogger(final Logger logger)
180     {
181         felixLogger = logger;
182     }
183 
184     public void setDisableMultipleBundleVersions(final boolean val)
185     {
186         disableMultipleBundleVersions = val;
187     }
188 
189     /**
190      * Clears export cache.
191      *
192      * @since 2.9.0
193      */
194     public void clearExportCache()
195     {
196         exportsBuilder.clearExportCache();
197     }
198 
199     @SuppressWarnings ({ "UnusedDeclaration" })
200     @PluginEventListener
201     public void onStart(final PluginFrameworkStartingEvent event)
202     {
203         start();
204     }
205 
206     @SuppressWarnings ({ "UnusedDeclaration" })
207     @PluginEventListener
208     public void onShutdown(final PluginFrameworkShutdownEvent event)
209     {
210         stop();
211     }
212 
213     @SuppressWarnings ({ "UnusedDeclaration" })
214     @PluginEventListener
215     public void onPluginUpgrade(PluginUpgradedEvent event)
216     {
217         registration.refreshPackages();
218     }
219 
220     @SuppressWarnings ({ "UnusedDeclaration" })
221     @PluginEventListener
222     public void onPluginUninstallation(PluginUninstalledEvent event)
223     {
224         registration.refreshPackages();
225     }
226 
227     @SuppressWarnings ({ "UnusedDeclaration" })
228     @PluginEventListener
229     public void onPluginFrameworkWarmRestarting(PluginFrameworkWarmRestartingEvent event)
230     {
231         registration.loadHostComponents(collectHostComponents(hostComponentProvider));
232     }
233 
234     public void start() throws OsgiContainerException
235     {
236         if (isRunning())
237         {
238             return;
239         }
240 
241         final DefaultComponentRegistrar registrar = collectHostComponents(hostComponentProvider);
242         // Create a case-insensitive configuration property map.
243         final StringMap configMap = new StringMap(false);
244 
245         // Add the bundle provided service interface package and the core OSGi
246         // packages to be exported from the class path via the system bundle.
247         configMap.put(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, exportsBuilder.getExports(registrar.getRegistry(), packageScannerConfig));
248 
249         // Explicitly specify the directory to use for caching bundles.
250         configMap.put(BundleCache.CACHE_ROOTDIR_PROP, persistentCache.getOsgiBundleCache().getAbsolutePath());
251 
252         configMap.put(FelixConstants.LOG_LEVEL_PROP, String.valueOf(felixLogger.getLogLevel()));
253         configMap.put(FelixConstants.LOG_LOGGER_PROP, felixLogger);
254         String bootDelegation = getAtlassianSpecificOsgiSystemProperty(OSGI_BOOTDELEGATION);
255         if ((bootDelegation == null) || (bootDelegation.trim().length() == 0))
256         {
257             // These exist to work around JAXP problems.  Specifically, bundles that use static factories to create JAXP
258             // instances will execute FactoryFinder with the CCL set to the bundle.  These delegations ensure the appropriate
259             // implementation is found and loaded.
260             bootDelegation = "weblogic,weblogic.*," +
261                              "META-INF.services," +
262                              "com.yourkit,com.yourkit.*," +
263                              "com.chronon,com.chronon.*," +
264                              "com.jprofiler,com.jprofiler.*," +
265                              "org.apache.xerces,org.apache.xerces.*," +
266                              "org.apache.xalan,org.apache.xalan.*," +
267                              "org.apache.xml.serializer," +
268                              "sun.*," +
269                              "com.sun.xml.bind.v2," +
270                              "com.icl.saxon";
271         }
272 
273         configMap.put(FelixConstants.FRAMEWORK_BOOTDELEGATION, bootDelegation);
274         configMap.put(FelixConstants.IMPLICIT_BOOT_DELEGATION_PROP, "false");
275 
276         configMap.put(FelixConstants.FRAMEWORK_BUNDLE_PARENT, FelixConstants.FRAMEWORK_BUNDLE_PARENT_FRAMEWORK);
277         if (log.isDebugEnabled())
278         {
279             log.debug("Felix configuration: " + configMap);
280         }
281 
282         validateConfiguration(configMap);
283 
284         try
285         {
286             // Create host activator;
287             registration = new BundleRegistration(frameworkBundlesUrl, persistentCache.getFrameworkBundleCache(), registrar);
288             final List<BundleActivator> list = new ArrayList<BundleActivator>();
289             list.add(registration);
290             configMap.put(FelixConstants.SYSTEMBUNDLE_ACTIVATORS_PROP, list);
291 
292             // Now create an instance of the framework with
293             // our configuration properties and activator.
294             felix = new Felix(configMap);
295 
296             // Now start Felix instance.  Starting in a different thread to explicity set daemon status
297             final Runnable start = new Runnable()
298             {
299                 public void run()
300                 {
301                     try
302                     {
303                         Thread.currentThread().setContextClassLoader(null);
304                         felix.start();
305                         felixRunning = true;
306                     }
307                     catch (final BundleException e)
308                     {
309                         throw new OsgiContainerException("Unable to start felix", e);
310                     }
311                 }
312             };
313             final Thread t = threadFactory.newThread(start);
314             t.start();
315 
316             // Give it 10 seconds
317             t.join(10 * 60 * 1000);
318 
319         }
320         catch (final Exception ex)
321         {
322             throw new OsgiContainerException("Unable to start OSGi container", ex);
323         }
324         pluginEventManager.broadcast(new OsgiContainerStartedEvent(this));
325     }
326 
327     /**
328      *
329      * @param configMap The Felix configuration
330      * @throws OsgiContainerException If any validation fails
331      */
332     private void validateConfiguration(StringMap configMap) throws OsgiContainerException
333     {
334         String systemExports = (String) configMap.get(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA);
335         // Change in JVM should trigger the cache cleardown, regardless of export changes.
336         String cacheKeySource = StringUtils.join(new Object[] {this.getRuntimeEnvironment(), systemExports }, ',');
337 
338         validateCaches(cacheKeySource);
339 
340         detectIncorrectOsgiVersion();
341         detectXercesOverride(systemExports);
342     }
343 
344     /**
345      * Detect when xerces has no version, most likely due to an installation of Tomcat where an old version of xerces
346      * is installed into common/lib/endorsed in order to support Java 1.4.
347      *
348      * @param systemExports The system exports
349      * @throws OsgiContainerException If xerces has no version
350      */
351     void detectXercesOverride(String systemExports) throws OsgiContainerException
352     {
353         int pos = systemExports.indexOf("org.apache.xerces.util");
354         if (pos > -1)
355         {
356             if (pos == 0 || (pos > 0 && systemExports.charAt(pos - 1) == ','))
357             {
358                 pos += "org.apache.xerces.util".length();
359 
360                 // only fail if no xerces found and xerces has no version
361                 if (pos >= systemExports.length() || ';' != systemExports.charAt(pos))
362                 {
363                     throw new OsgiContainerException(
364                             "Detected an incompatible version of Apache Xerces on the classpath.  If using Tomcat, you may have " +
365                                     "an old version of Xerces in $TOMCAT_HOME/common/lib/endorsed that will need to be removed.");
366                 }
367             }
368         }
369     }
370 
371     /**
372      * Validate caches based on the list of packages and the runtime environment exported from the application.
373      * If the settings have changed, the cache directories should be cleared.
374      *
375      * @param cacheKeySource string of information that generates the cache key.
376      */
377     private void validateCaches(String cacheKeySource)
378     {
379         log.info("Using Felix bundle cacheKey source: {}", cacheKeySource);
380         persistentCache.validate(cacheKeySource);
381         log.debug("Using Felix bundle cache directory: {}", persistentCache.getOsgiBundleCache().getAbsolutePath());
382     }
383 
384     /**
385      * Detects incorrect configuration of WebSphere 6.1 that leaks OSGi 4.0 jars into the application
386      */
387     private void detectIncorrectOsgiVersion()
388     {
389         try
390         {
391             Bundle.class.getMethod("getBundleContext");
392         }
393         catch (final NoSuchMethodException e)
394         {
395             throw new OsgiContainerException(
396                 "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.");
397         }
398     }
399 
400     public void stop() throws OsgiContainerException
401     {
402         if (felixRunning)
403         {
404             for (final ServiceTracker tracker : new HashSet<ServiceTracker>(trackers))
405             {
406                 tracker.close();
407             }
408             try
409             {
410                 felix.stop();
411                 felix.waitForStop(5000);
412             }
413             catch (InterruptedException e)
414             {
415                 log.warn("Interrupting Felix shutdown", e);
416             }
417             catch (BundleException ex)
418             {
419                 log.error("An error occurred while stopping the Felix OSGi Container. ", ex);
420             }
421         }
422 
423         felixRunning = false;
424         felix = null;
425         pluginEventManager.broadcast(new OsgiContainerStoppedEvent(this));
426     }
427 
428     public Bundle[] getBundles()
429     {
430         if (isRunning())
431         {
432             return registration.getBundles();
433         }
434         else
435         {
436             throw new IllegalStateException(
437                 "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.");
438         }
439     }
440 
441     public ServiceReference[] getRegisteredServices()
442     {
443         return felix.getRegisteredServices();
444     }
445 
446     public ServiceTracker getServiceTracker(final String interfaceClassName)
447     {
448         return getServiceTracker(interfaceClassName, null);
449     }
450 
451     @Override
452     public ServiceTracker getServiceTracker(String interfaceClassName, ServiceTrackerCustomizer serviceTrackerCustomizer)
453     {
454         if (!isRunning())
455         {
456             throw new IllegalStateException("Unable to create a tracker when osgi is not running");
457         }
458 
459         final ServiceTracker tracker = registration.getServiceTracker(interfaceClassName, trackers, serviceTrackerCustomizer);
460         tracker.open();
461         trackers.add(tracker);
462         return tracker;
463     }
464 
465     public Bundle installBundle(final File file) throws OsgiContainerException
466     {
467         try
468         {
469             return registration.install(file, disableMultipleBundleVersions);
470         }
471         catch (final BundleException e)
472         {
473             throw new OsgiContainerException("Unable to install bundle", e);
474         }
475     }
476 
477     DefaultComponentRegistrar collectHostComponents(final HostComponentProvider provider)
478     {
479         final DefaultComponentRegistrar registrar = new DefaultComponentRegistrar();
480         if (provider != null)
481         {
482             provider.provide(registrar);
483         }
484         return registrar;
485     }
486 
487     public boolean isRunning()
488     {
489         return felixRunning;
490     }
491 
492     public List<HostComponentRegistration> getHostComponentRegistrations()
493     {
494         return registration.getHostComponentRegistrations();
495     }
496 
497     private String getAtlassianSpecificOsgiSystemProperty(final String originalSystemProperty)
498     {
499         return System.getProperty(ATLASSIAN_PREFIX + originalSystemProperty);
500     }
501 
502     /**
503      * Get a string containing the significant runtime environment details that may require a rebuild of the OSGi cache.
504      * @return a string containing significant runtime details.
505      */
506     @VisibleForTesting
507     String getRuntimeEnvironment()
508     {
509         //Two things currently:
510         // - JDK version
511         // - Startup timeout which may be cached in the transformed plugins.
512         return String.format("java.version=%s,plugin.enable.timeout=%d", System.getProperty("java.version"), PluginUtils.getDefaultEnablingWaitPeriod());
513     }
514 
515     /**
516      * Manages framework-level framework bundles and host components registration, and individual plugin bundle
517      * installation and removal.
518      */
519     static class BundleRegistration implements BundleActivator, BundleListener, FrameworkListener
520     {
521         private BundleContext bundleContext;
522         private DefaultComponentRegistrar registrar;
523         private List<ServiceRegistration> hostServicesReferences;
524         private List<HostComponentRegistration> hostComponentRegistrations;
525         private final URL frameworkBundlesUrl;
526         private final File frameworkBundlesDir;
527         private ClassLoader initializedClassLoader;
528         private PackageAdmin packageAdmin;
529 
530         public BundleRegistration(final URL frameworkBundlesUrl, final File frameworkBundlesDir, final DefaultComponentRegistrar registrar)
531         {
532             this.registrar = registrar;
533             this.frameworkBundlesUrl = frameworkBundlesUrl;
534             this.frameworkBundlesDir = frameworkBundlesDir;
535             this.initializedClassLoader = Thread.currentThread().getContextClassLoader();
536         }
537 
538         public void start(final BundleContext context) throws Exception
539         {
540             bundleContext = context;
541             final ServiceReference ref = context.getServiceReference(org.osgi.service.packageadmin.PackageAdmin.class.getName());
542             packageAdmin = (PackageAdmin) context.getService(ref);
543 
544             context.addBundleListener(this);
545             context.addFrameworkListener(this);
546 
547             loadHostComponents(registrar);
548             extractAndInstallFrameworkBundles();
549         }
550 
551         public void stop(final BundleContext ctx) throws Exception
552         {
553             ctx.removeBundleListener(this);
554             ctx.removeFrameworkListener(this);
555             if (hostServicesReferences != null)
556             {
557                 for (ServiceRegistration ref : hostServicesReferences)
558                 {
559                     ref.unregister();
560                 }
561             }
562             bundleContext = null;
563             packageAdmin = null;
564             hostServicesReferences = null;
565             hostComponentRegistrations = null;
566             registrar = null;
567             initializedClassLoader = null;
568         }
569 
570         public void bundleChanged(final BundleEvent evt)
571         {
572             switch (evt.getType())
573             {
574                 case BundleEvent.INSTALLED:
575                     log.info("Installed bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
576                     break;
577                 case BundleEvent.RESOLVED:
578                     log.info("Resolved bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
579                     break;
580                 case BundleEvent.UNRESOLVED:
581                     log.info("Unresolved bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
582                     break;
583                 case BundleEvent.STARTED:
584                     log.info("Started bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
585                     break;
586                 case BundleEvent.STOPPED:
587                     log.info("Stopped bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
588                     break;
589                 case BundleEvent.UNINSTALLED:
590                     log.info("Uninstalled bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
591                     break;
592             }
593         }
594 
595         public Bundle install(final File path, final boolean uninstallOtherVersions) throws BundleException
596         {
597             boolean bundleUninstalled = false;
598             if (uninstallOtherVersions)
599             {
600                 try
601                 {
602                     JarFile jar = new JarFile(path);
603                     String pluginKey = null;
604                     try
605                     {
606                         pluginKey = OsgiHeaderUtil.getPluginKey(jar.getManifest());
607                     }
608                     finally
609                     {
610                         jar.close();
611                     }
612                     for (final Bundle oldBundle : bundleContext.getBundles())
613                     {
614                         if (pluginKey.equals(OsgiHeaderUtil.getPluginKey(oldBundle)))
615                         {
616                             log.info("Uninstalling existing version " + oldBundle.getHeaders().get(Constants.BUNDLE_VERSION));
617                             oldBundle.uninstall();
618                             bundleUninstalled = true;
619                         }
620                     }
621                 }
622                 catch (final IOException e)
623                 {
624                     throw new BundleException("Invalid bundle format", e);
625                 }
626             }
627             final Bundle bundle = bundleContext.installBundle(path.toURI().toString());
628             if (bundleUninstalled)
629             {
630                 refreshPackages();
631             }
632             return bundle;
633         }
634 
635         public Bundle[] getBundles()
636         {
637             return bundleContext.getBundles();
638         }
639 
640         public ServiceTracker getServiceTracker(final String clazz,
641                                                 final Collection<ServiceTracker> trackedTrackers)
642         {
643             return getServiceTracker(clazz, trackedTrackers, null);
644         }
645 
646         public ServiceTracker getServiceTracker(final String clazz,
647                                                 final Collection<ServiceTracker> trackedTrackers,
648                                                 final ServiceTrackerCustomizer customizer)
649         {
650             return new ServiceTracker(bundleContext, clazz, customizer)
651             {
652                 @Override
653                 public void close()
654                 {
655                     super.close();
656                     trackedTrackers.remove(this);
657                 }
658             };
659         }
660 
661         public List<HostComponentRegistration> getHostComponentRegistrations()
662         {
663             return hostComponentRegistrations;
664         }
665 
666         void loadHostComponents(final DefaultComponentRegistrar registrar)
667         {
668             // Unregister any existing host components
669             if (hostServicesReferences != null)
670             {  
671                 for (final ServiceRegistration reg : hostServicesReferences)
672                 {
673                     reg.unregister();
674                 }
675             }
676 
677             ContextClassLoaderSwitchingUtil.runInContext(initializedClassLoader, new Runnable()
678             {
679                 public void run()
680                 {
681                     hostServicesReferences = registrar.writeRegistry(bundleContext);
682                     hostComponentRegistrations = registrar.getRegistry();
683                 }
684             });
685         }
686 
687         private void extractAndInstallFrameworkBundles() throws BundleException
688         {
689             final List<Bundle> bundles = new ArrayList<Bundle>();
690             com.atlassian.plugin.util.FileUtils.conditionallyExtractZipFile(frameworkBundlesUrl, frameworkBundlesDir);
691             for (final File bundleFile : frameworkBundlesDir.listFiles(new FilenameFilter()
692                 {
693                     public boolean accept(final File file, final String s)
694                     {
695                         return s.endsWith(".jar");
696                     }
697                 }))
698             {
699                 bundles.add(install(bundleFile, false));
700             }
701 
702             packageAdmin.resolveBundles(null);
703 
704             for (final Bundle bundle : bundles)
705             {
706                 if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) == null)
707                 {
708                     bundle.start();
709                 }
710             }
711         }
712 
713         public void refreshPackages()
714         {
715             final CountDownLatch latch = new CountDownLatch(1);
716             FrameworkListener refreshListener = new FrameworkListener()
717             {
718 
719                 public void frameworkEvent(FrameworkEvent event)
720                 {
721                     if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED)
722                     {
723                         log.info("Packages refreshed");
724                         latch.countDown();
725                     }
726                 }
727             };
728 
729             bundleContext.addFrameworkListener(refreshListener);
730             try
731             {
732                 packageAdmin.refreshPackages(null);
733                 boolean refreshed = false;
734                 try
735                 {
736                     refreshed = latch.await(REFRESH_TIMEOUT, TimeUnit.SECONDS);
737                 }
738                 catch (InterruptedException e)
739                 {
740                     // ignore
741                 }
742                 if (!refreshed)
743                 {
744                     log.warn("Timeout exceeded waiting for package refresh");
745                 }
746             }
747             finally
748             {
749                 bundleContext.removeFrameworkListener(refreshListener);
750             }
751         }
752 
753         @SuppressWarnings ({ "ThrowableResultOfMethodCallIgnored" })
754         public void frameworkEvent(FrameworkEvent event)
755         {
756             String bundleBits = "";
757             if (event.getBundle() != null)
758             {
759                 bundleBits = " in bundle " + event.getBundle().getSymbolicName();
760             }
761             switch (event.getType())
762             {
763                 case FrameworkEvent.ERROR:
764                     log.error("Framework error" + bundleBits, event.getThrowable());
765                     break;
766                 case FrameworkEvent.WARNING:
767                     log.warn("Framework warning" + bundleBits, event.getThrowable());
768                     break;
769                 case FrameworkEvent.INFO:
770                     log.info("Framework info" + bundleBits, event.getThrowable());
771                     break;
772             }
773         }
774     }
775 
776 }