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                              "com_cenqua_clover," +
273                              "com.cenqua.clover,com.cenqua.clover.*," +
274                              "com.atlassian.clover,com.atlassian.clover.*";
275         }
276 
277         configMap.put(FelixConstants.FRAMEWORK_BOOTDELEGATION, bootDelegation);
278         configMap.put(FelixConstants.IMPLICIT_BOOT_DELEGATION_PROP, "false");
279 
280         configMap.put(FelixConstants.FRAMEWORK_BUNDLE_PARENT, FelixConstants.FRAMEWORK_BUNDLE_PARENT_FRAMEWORK);
281         if (log.isDebugEnabled())
282         {
283             log.debug("Felix configuration: " + configMap);
284         }
285 
286         validateConfiguration(configMap);
287 
288         try
289         {
290             // Create host activator;
291             registration = new BundleRegistration(frameworkBundlesUrl, persistentCache.getFrameworkBundleCache(), registrar);
292             final List<BundleActivator> list = new ArrayList<BundleActivator>();
293             list.add(registration);
294             configMap.put(FelixConstants.SYSTEMBUNDLE_ACTIVATORS_PROP, list);
295 
296             // Now create an instance of the framework with
297             // our configuration properties and activator.
298             felix = new Felix(configMap);
299 
300             // Now start Felix instance.  Starting in a different thread to explicity set daemon status
301             final Runnable start = new Runnable()
302             {
303                 public void run()
304                 {
305                     try
306                     {
307                         Thread.currentThread().setContextClassLoader(null);
308                         felix.start();
309                         felixRunning = true;
310                     }
311                     catch (final BundleException e)
312                     {
313                         throw new OsgiContainerException("Unable to start felix", e);
314                     }
315                 }
316             };
317             final Thread t = threadFactory.newThread(start);
318             t.start();
319 
320             // Give it 10 seconds
321             t.join(10 * 60 * 1000);
322 
323         }
324         catch (final Exception ex)
325         {
326             throw new OsgiContainerException("Unable to start OSGi container", ex);
327         }
328         pluginEventManager.broadcast(new OsgiContainerStartedEvent(this));
329     }
330 
331     /**
332      *
333      * @param configMap The Felix configuration
334      * @throws OsgiContainerException If any validation fails
335      */
336     private void validateConfiguration(StringMap configMap) throws OsgiContainerException
337     {
338         String systemExports = (String) configMap.get(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA);
339         // Change in JVM should trigger the cache cleardown, regardless of export changes.
340         String cacheKeySource = StringUtils.join(new Object[] {this.getRuntimeEnvironment(), systemExports }, ',');
341 
342         validateCaches(cacheKeySource);
343 
344         detectIncorrectOsgiVersion();
345         detectXercesOverride(systemExports);
346     }
347 
348     /**
349      * Detect when xerces has no version, most likely due to an installation of Tomcat where an old version of xerces
350      * is installed into common/lib/endorsed in order to support Java 1.4.
351      *
352      * @param systemExports The system exports
353      * @throws OsgiContainerException If xerces has no version
354      */
355     void detectXercesOverride(String systemExports) throws OsgiContainerException
356     {
357         int pos = systemExports.indexOf("org.apache.xerces.util");
358         if (pos > -1)
359         {
360             if (pos == 0 || (pos > 0 && systemExports.charAt(pos - 1) == ','))
361             {
362                 pos += "org.apache.xerces.util".length();
363 
364                 // only fail if no xerces found and xerces has no version
365                 if (pos >= systemExports.length() || ';' != systemExports.charAt(pos))
366                 {
367                     throw new OsgiContainerException(
368                             "Detected an incompatible version of Apache Xerces on the classpath.  If using Tomcat, you may have " +
369                                     "an old version of Xerces in $TOMCAT_HOME/common/lib/endorsed that will need to be removed.");
370                 }
371             }
372         }
373     }
374 
375     /**
376      * Validate caches based on the list of packages and the runtime environment exported from the application.
377      * If the settings have changed, the cache directories should be cleared.
378      *
379      * @param cacheKeySource string of information that generates the cache key.
380      */
381     private void validateCaches(String cacheKeySource)
382     {
383         log.info("Using Felix bundle cacheKey source: {}", cacheKeySource);
384         persistentCache.validate(cacheKeySource);
385         log.debug("Using Felix bundle cache directory: {}", persistentCache.getOsgiBundleCache().getAbsolutePath());
386     }
387 
388     /**
389      * Detects incorrect configuration of WebSphere 6.1 that leaks OSGi 4.0 jars into the application
390      */
391     private void detectIncorrectOsgiVersion()
392     {
393         try
394         {
395             Bundle.class.getMethod("getBundleContext");
396         }
397         catch (final NoSuchMethodException e)
398         {
399             throw new OsgiContainerException(
400                 "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.");
401         }
402     }
403 
404     public void stop() throws OsgiContainerException
405     {
406         if (felixRunning)
407         {
408             for (final ServiceTracker tracker : new HashSet<ServiceTracker>(trackers))
409             {
410                 tracker.close();
411             }
412             FrameworkListener listener = new FrameworkListener()
413             {
414                 @Override
415                 public void frameworkEvent(FrameworkEvent event)
416                 {
417                     if (event.getType() == FrameworkEvent.WAIT_TIMEDOUT)
418                     {
419                         log.error("Timeout waiting for OSGi to shutdown");
420                         threadDump();
421                     } else if (event.getType() == FrameworkEvent.STOPPED)
422                     {
423                         log.info("OSGi shutdown successful");
424                     }
425                 }
426             };
427             try
428             {
429                 felix.getBundleContext().addFrameworkListener(listener);
430                 felix.stop();
431                 felix.waitForStop(TimeUnit.SECONDS.toMillis(60));
432             }
433             catch (InterruptedException e)
434             {
435                 log.warn("Interrupting Felix shutdown", e);
436             }
437             catch (BundleException ex)
438             {
439                 log.error("An error occurred while stopping the Felix OSGi Container. ", ex);
440             }
441         }
442 
443         felixRunning = false;
444         felix = null;
445         pluginEventManager.broadcast(new OsgiContainerStoppedEvent(this));
446     }
447 
448     private void threadDump()
449     {
450         StringBuilder sb = new StringBuilder();
451         String nl = System.getProperty("line.separator");
452         for (Map.Entry<Thread,StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet())
453         {
454             Thread key = entry.getKey();
455             StackTraceElement[] trace = entry.getValue();
456             sb.append(key).append(nl);
457             for (StackTraceElement aTrace : trace)
458             {
459                 sb.append(" ").append(aTrace).append(nl);
460             }
461         }
462         log.debug("Thread dump: " + nl + sb.toString());
463     }
464 
465     public Bundle[] getBundles()
466     {
467         if (isRunning())
468         {
469             return registration.getBundles();
470         }
471         else
472         {
473             throw new IllegalStateException(
474                 "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.");
475         }
476     }
477 
478     public ServiceReference[] getRegisteredServices()
479     {
480         return felix.getRegisteredServices();
481     }
482 
483     public ServiceTracker getServiceTracker(final String interfaceClassName)
484     {
485         return getServiceTracker(interfaceClassName, null);
486     }
487 
488     @Override
489     public ServiceTracker getServiceTracker(String interfaceClassName, ServiceTrackerCustomizer serviceTrackerCustomizer)
490     {
491         if (!isRunning())
492         {
493             throw new IllegalStateException("Unable to create a tracker when osgi is not running");
494         }
495 
496         final ServiceTracker tracker = registration.getServiceTracker(interfaceClassName, trackers, serviceTrackerCustomizer);
497         tracker.open();
498         trackers.add(tracker);
499         return tracker;
500     }
501 
502     public Bundle installBundle(final File file) throws OsgiContainerException
503     {
504         try
505         {
506             return registration.install(file, disableMultipleBundleVersions);
507         }
508         catch (final BundleException e)
509         {
510             throw new OsgiContainerException("Unable to install bundle", e);
511         }
512     }
513 
514     DefaultComponentRegistrar collectHostComponents(final HostComponentProvider provider)
515     {
516         final DefaultComponentRegistrar registrar = new DefaultComponentRegistrar();
517         if (provider != null)
518         {
519             provider.provide(registrar);
520         }
521         return registrar;
522     }
523 
524     public boolean isRunning()
525     {
526         return felixRunning;
527     }
528 
529     public List<HostComponentRegistration> getHostComponentRegistrations()
530     {
531         return registration.getHostComponentRegistrations();
532     }
533 
534     private String getAtlassianSpecificOsgiSystemProperty(final String originalSystemProperty)
535     {
536         return System.getProperty(ATLASSIAN_PREFIX + originalSystemProperty);
537     }
538 
539     /**
540      * Get a string containing the significant runtime environment details that may require a rebuild of the OSGi cache.
541      * @return a string containing significant runtime details.
542      */
543     @VisibleForTesting
544     String getRuntimeEnvironment()
545     {
546         //Two things currently:
547         // - JDK version
548         // - Startup timeout which may be cached in the transformed plugins.
549         return String.format("java.version=%s,plugin.enable.timeout=%d", System.getProperty("java.version"),
550                 PluginUtils.getDefaultEnablingWaitPeriod());
551     }
552 
553     /**
554      * Manages framework-level framework bundles and host components registration, and individual plugin bundle
555      * installation and removal.
556      */
557     static class BundleRegistration implements BundleActivator, BundleListener, FrameworkListener
558     {
559         private BundleContext bundleContext;
560         private DefaultComponentRegistrar registrar;
561         private List<ServiceRegistration> hostServicesReferences;
562         private List<HostComponentRegistration> hostComponentRegistrations;
563         private final URL frameworkBundlesUrl;
564         private final File frameworkBundlesDir;
565         private ClassLoader initializedClassLoader;
566         private PackageAdmin packageAdmin;
567 
568         public BundleRegistration(final URL frameworkBundlesUrl, final File frameworkBundlesDir, final DefaultComponentRegistrar registrar)
569         {
570             this.registrar = registrar;
571             this.frameworkBundlesUrl = frameworkBundlesUrl;
572             this.frameworkBundlesDir = frameworkBundlesDir;
573             this.initializedClassLoader = Thread.currentThread().getContextClassLoader();
574         }
575 
576         public void start(final BundleContext context) throws Exception
577         {
578             bundleContext = context;
579             final ServiceReference ref = context.getServiceReference(org.osgi.service.packageadmin.PackageAdmin.class.getName());
580             packageAdmin = (PackageAdmin) context.getService(ref);
581 
582             context.addBundleListener(this);
583             context.addFrameworkListener(this);
584 
585             loadHostComponents(registrar);
586             extractAndInstallFrameworkBundles();
587         }
588 
589         public void stop(final BundleContext ctx) throws Exception
590         {
591             ctx.removeBundleListener(this);
592             ctx.removeFrameworkListener(this);
593             if (hostServicesReferences != null)
594             {
595                 for (ServiceRegistration ref : hostServicesReferences)
596                 {
597                     ref.unregister();
598                 }
599             }
600             bundleContext = null;
601             packageAdmin = null;
602             hostServicesReferences = null;
603             hostComponentRegistrations = null;
604             registrar = null;
605             initializedClassLoader = null;
606         }
607 
608         public void bundleChanged(final BundleEvent evt)
609         {
610             switch (evt.getType())
611             {
612                 case BundleEvent.INSTALLED:
613                     log.info("Installed bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
614                     break;
615                 case BundleEvent.RESOLVED:
616                     log.info("Resolved bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
617                     break;
618                 case BundleEvent.UNRESOLVED:
619                     log.info("Unresolved bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
620                     break;
621                 case BundleEvent.STARTED:
622                     log.info("Started bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
623                     break;
624                 case BundleEvent.STOPPED:
625                     log.info("Stopped bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
626                     break;
627                 case BundleEvent.UNINSTALLED:
628                     log.info("Uninstalled bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
629                     break;
630             }
631         }
632 
633         public Bundle install(final File path, final boolean uninstallOtherVersions) throws BundleException
634         {
635             boolean bundleUninstalled = false;
636             if (uninstallOtherVersions)
637             {
638                 try
639                 {
640                     JarFile jar = new JarFile(path);
641                     String pluginKey = null;
642                     try
643                     {
644                         pluginKey = OsgiHeaderUtil.getPluginKey(jar.getManifest());
645                     }
646                     finally
647                     {
648                         jar.close();
649                     }
650                     for (final Bundle oldBundle : bundleContext.getBundles())
651                     {
652                         if (pluginKey.equals(OsgiHeaderUtil.getPluginKey(oldBundle)))
653                         {
654                             log.info("Uninstalling existing version " + oldBundle.getHeaders().get(Constants.BUNDLE_VERSION));
655                             oldBundle.uninstall();
656                             bundleUninstalled = true;
657                         }
658                     }
659                 }
660                 catch (final IOException e)
661                 {
662                     throw new BundleException("Invalid bundle format", e);
663                 }
664             }
665             final Bundle bundle = bundleContext.installBundle(path.toURI().toString());
666             if (bundleUninstalled)
667             {
668                 refreshPackages();
669             }
670             return bundle;
671         }
672 
673         public Bundle[] getBundles()
674         {
675             return bundleContext.getBundles();
676         }
677 
678         public ServiceTracker getServiceTracker(final String clazz,
679                                                 final Collection<ServiceTracker> trackedTrackers)
680         {
681             return getServiceTracker(clazz, trackedTrackers, null);
682         }
683 
684         public ServiceTracker getServiceTracker(final String clazz,
685                                                 final Collection<ServiceTracker> trackedTrackers,
686                                                 final ServiceTrackerCustomizer customizer)
687         {
688             return new ServiceTracker(bundleContext, clazz, customizer)
689             {
690                 @Override
691                 public void close()
692                 {
693                     super.close();
694                     trackedTrackers.remove(this);
695                 }
696             };
697         }
698 
699         public List<HostComponentRegistration> getHostComponentRegistrations()
700         {
701             return hostComponentRegistrations;
702         }
703 
704         void loadHostComponents(final DefaultComponentRegistrar registrar)
705         {
706             // Unregister any existing host components
707             if (hostServicesReferences != null)
708             {  
709                 for (final ServiceRegistration reg : hostServicesReferences)
710                 {
711                     reg.unregister();
712                 }
713             }
714 
715             ContextClassLoaderSwitchingUtil.runInContext(initializedClassLoader, new Runnable()
716             {
717                 public void run()
718                 {
719                     hostServicesReferences = registrar.writeRegistry(bundleContext);
720                     hostComponentRegistrations = registrar.getRegistry();
721                 }
722             });
723         }
724 
725         private void extractAndInstallFrameworkBundles() throws BundleException
726         {
727             final List<Bundle> bundles = new ArrayList<Bundle>();
728             com.atlassian.plugin.util.FileUtils.conditionallyExtractZipFile(frameworkBundlesUrl, frameworkBundlesDir);
729             for (final File bundleFile : frameworkBundlesDir.listFiles(new FilenameFilter()
730                 {
731                     public boolean accept(final File file, final String s)
732                     {
733                         return s.endsWith(".jar");
734                     }
735                 }))
736             {
737                 bundles.add(install(bundleFile, false));
738             }
739 
740             packageAdmin.resolveBundles(null);
741 
742             for (final Bundle bundle : bundles)
743             {
744                 if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) == null)
745                 {
746                     bundle.start();
747                 }
748             }
749         }
750 
751         public void refreshPackages()
752         {
753             final CountDownLatch latch = new CountDownLatch(1);
754             FrameworkListener refreshListener = new FrameworkListener()
755             {
756 
757                 public void frameworkEvent(FrameworkEvent event)
758                 {
759                     if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED)
760                     {
761                         log.info("Packages refreshed");
762                         latch.countDown();
763                     }
764                 }
765             };
766 
767             bundleContext.addFrameworkListener(refreshListener);
768             try
769             {
770                 packageAdmin.refreshPackages(null);
771                 boolean refreshed = false;
772                 try
773                 {
774                     refreshed = latch.await(REFRESH_TIMEOUT, TimeUnit.SECONDS);
775                 }
776                 catch (InterruptedException e)
777                 {
778                     // ignore
779                 }
780                 if (!refreshed)
781                 {
782                     log.warn("Timeout exceeded waiting for package refresh");
783                 }
784             }
785             finally
786             {
787                 bundleContext.removeFrameworkListener(refreshListener);
788             }
789         }
790 
791         @SuppressWarnings ({ "ThrowableResultOfMethodCallIgnored" })
792         public void frameworkEvent(FrameworkEvent event)
793         {
794             String bundleBits = "";
795             if (event.getBundle() != null)
796             {
797                 bundleBits = " in bundle " + event.getBundle().getSymbolicName();
798             }
799             switch (event.getType())
800             {
801                 case FrameworkEvent.ERROR:
802                     log.error("Framework error" + bundleBits, event.getThrowable());
803                     break;
804                 case FrameworkEvent.WARNING:
805                     log.warn("Framework warning" + bundleBits, event.getThrowable());
806                     break;
807                 case FrameworkEvent.INFO:
808                     log.info("Framework info" + bundleBits, event.getThrowable());
809                     break;
810             }
811         }
812     }
813 
814 }