View Javadoc

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