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