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