View Javadoc

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