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.osgi.container.OsgiContainerException;
8   import com.atlassian.plugin.osgi.container.OsgiContainerManager;
9   import com.atlassian.plugin.osgi.container.PackageScannerConfiguration;
10  import com.atlassian.plugin.osgi.hostcomponents.HostComponentProvider;
11  import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
12  import com.atlassian.plugin.osgi.hostcomponents.impl.DefaultComponentRegistrar;
13  import com.atlassian.plugin.osgi.util.OsgiHeaderUtil;
14  import com.atlassian.plugin.util.ClassLoaderUtils;
15  import org.apache.commons.io.FileUtils;
16  import org.apache.commons.lang.Validate;
17  import org.apache.commons.logging.Log;
18  import org.apache.commons.logging.LogFactory;
19  import org.apache.felix.framework.Felix;
20  import org.apache.felix.framework.Logger;
21  import org.apache.felix.framework.cache.BundleCache;
22  import org.apache.felix.framework.util.FelixConstants;
23  import org.apache.felix.framework.util.StringMap;
24  import org.osgi.framework.*;
25  import org.osgi.service.packageadmin.PackageAdmin;
26  import org.osgi.util.tracker.ServiceTracker;
27  
28  import java.io.File;
29  import java.io.FilenameFilter;
30  import java.io.IOException;
31  import java.net.URL;
32  import java.util.*;
33  import java.util.concurrent.CopyOnWriteArraySet;
34  import java.util.jar.JarFile;
35  
36  /**
37   * Felix implementation of the OSGi container manager
38   */
39  public class FelixOsgiContainerManager implements OsgiContainerManager
40  {
41      private static final Log log = LogFactory.getLog(FelixOsgiContainerManager.class);
42      public static final String OSGI_FRAMEWORK_BUNDLES_ZIP = "osgi-framework-bundles.zip";
43      private static final String OSGI_BOOTDELEGATION = "org.osgi.framework.bootdelegation";
44      private static final String ATLASSIAN_PREFIX = "atlassian.";
45      private BundleRegistration registration = null;
46      private Felix felix = null;
47      private boolean felixRunning = false;
48      private File cacheDirectory;
49      private boolean disableMultipleBundleVersions = true;
50  
51      private final URL frameworkBundlesUrl;
52      private PackageScannerConfiguration packageScannerConfig;
53      private File frameworkBundlesDir;
54      private HostComponentProvider hostComponentProvider;
55      private Logger felixLogger;
56      private final Set<ServiceTracker> trackers;
57  
58      /**
59       * Constructs the container manager using the framework bundles zip file located in this library
60       * @param frameworkBundlesDir The directory to unzip the framework bundles into.
61       * @param packageScannerConfig The configuration for package scanning
62       * @param provider The host component provider.  May be null.
63       * @param eventManager The plugin event manager to register for init and shutdown events
64       */
65      public FelixOsgiContainerManager(File frameworkBundlesDir, PackageScannerConfiguration packageScannerConfig,
66                                       HostComponentProvider provider, PluginEventManager eventManager)
67      {
68          this(ClassLoaderUtils.getResource(OSGI_FRAMEWORK_BUNDLES_ZIP, FelixOsgiContainerManager.class), frameworkBundlesDir,
69                  packageScannerConfig, provider, eventManager);
70      }
71  
72      /**
73       * Constructs the container manager
74       * @param frameworkBundlesZip The location of the zip file containing framework bundles
75       * @param frameworkBundlesDir The directory to unzip the framework bundles into.
76       * @param packageScannerConfig The configuration for package scanning
77       * @param provider The host component provider.  May be null.
78       * @param eventManager The plugin event manager to register for init and shutdown events
79       */
80      public FelixOsgiContainerManager(URL frameworkBundlesZip, File frameworkBundlesDir,
81                                       PackageScannerConfiguration packageScannerConfig, HostComponentProvider provider,
82                                       PluginEventManager eventManager)
83      {
84          Validate.notNull(frameworkBundlesZip, "The framework bundles zip is required");
85          Validate.notNull(frameworkBundlesDir, "The framework bundles directory must not be null");
86          Validate.notNull(packageScannerConfig, "The package scanner configuration must not be null");
87          Validate.notNull(eventManager, "The plugin event manager is required");
88  
89          this.frameworkBundlesUrl = frameworkBundlesZip;
90          this.packageScannerConfig = packageScannerConfig;
91          this.frameworkBundlesDir = frameworkBundlesDir;
92          this.hostComponentProvider = provider;
93          this.trackers = Collections.synchronizedSet(new HashSet<ServiceTracker>());
94          eventManager.register(this);
95          felixLogger = new FelixLoggerBridge(log);
96      }
97  
98      public void setFelixLogger(Logger logger)
99      {
100         this.felixLogger = logger;
101     }
102 
103     public void setDisableMultipleBundleVersions(boolean val)
104     {
105         this.disableMultipleBundleVersions = val;
106     }
107 
108     @PluginEventListener
109     public void onStart(PluginFrameworkStartingEvent event)
110     {
111         start();
112     }
113 
114     @PluginEventListener
115     public void onShutdown(PluginFrameworkShutdownEvent event)
116     {
117         stop();
118     }
119 
120 
121     public void start() throws OsgiContainerException
122     {
123         if (isRunning())
124             return;
125 
126         detectIncorrectOsgiVersion();
127 
128         initialiseCacheDirectory();
129 
130         DefaultComponentRegistrar registrar = collectHostComponents(hostComponentProvider);
131         // Create a case-insensitive configuration property map.
132         final StringMap configMap = new StringMap(false);
133         // Configure the Felix instance to be embedded.
134         configMap.put(FelixConstants.EMBEDDED_EXECUTION_PROP, "true");
135         // Add the bundle provided service interface package and the core OSGi
136         // packages to be exported from the class path via the system bundle.
137         configMap.put(Constants.FRAMEWORK_SYSTEMPACKAGES, OsgiHeaderUtil.determineExports(registrar.getRegistry(), packageScannerConfig));
138 
139         // Explicitly specify the directory to use for caching bundles.
140         configMap.put(BundleCache.CACHE_PROFILE_DIR_PROP, cacheDirectory.getAbsolutePath());
141 
142         configMap.put(FelixConstants.LOG_LEVEL_PROP, String.valueOf(felixLogger.getLogLevel()));
143         String bootDelegation = getAtlassianSpecificOsgiSystemProperty(OSGI_BOOTDELEGATION);
144         if (bootDelegation == null || bootDelegation.trim().length() == 0)
145         {
146             bootDelegation = "weblogic.*,META-INF.services,com.yourkit.*,com.jprofiler.*,org.apache.xerces.*";
147         }
148 
149         configMap.put(Constants.FRAMEWORK_BOOTDELEGATION, bootDelegation);
150         if (log.isDebugEnabled())
151         {
152             log.debug("Felix configuration: "+configMap);
153         }
154 
155         try
156         {
157             // Create host activator;
158             registration = new BundleRegistration(frameworkBundlesUrl, frameworkBundlesDir, registrar);
159             final List<BundleActivator> list = new ArrayList<BundleActivator>();
160             list.add(registration);
161 
162             // Now create an instance of the framework with
163             // our configuration properties and activator.
164             felix = new Felix(felixLogger, configMap, list);
165 
166             // Now start Felix instance.  Starting in a different thread to explicity set daemon status
167             Runnable start = new Runnable() {
168                 public void run() {
169                     try
170                     {
171                         felix.start();
172                         felixRunning = true;
173                     } catch (BundleException e)
174                     {
175                         throw new OsgiContainerException("Unable to start felix", e);
176                     }
177                 }
178             };
179             Thread t = new Thread(start);
180             t.setDaemon(true);
181             t.start();
182 
183             // Give it 10 seconds
184             t.join(10 * 60 * 1000);
185         }
186         catch (Exception ex)
187         {
188             throw new OsgiContainerException("Unable to start OSGi container", ex);
189         }
190     }
191 
192     /**
193      * Detects incorrect configuration of WebSphere 6.1 that leaks OSGi 4.0 jars into the application
194      */
195     private void detectIncorrectOsgiVersion()
196     {
197         try
198         {
199             Bundle.class.getMethod("getBundleContext");
200         }
201         catch (NoSuchMethodException e)
202         {
203             throw new OsgiContainerException("Detected older version (4.0 or earlier) of OSGi.  If using WebSphere "+
204                 "6.1, please enable application-first (parent-last) classloading and the 'Single classloader for "+
205                 "application' WAR classloader policy.");
206         }
207     }
208 
209     public void stop() throws OsgiContainerException
210     {
211         try
212         {
213             if (felixRunning)
214             {
215                 for (ServiceTracker tracker : new HashSet<ServiceTracker>(trackers))
216                     tracker.close();
217                 felix.stopAndWait();
218             }
219 
220             if (cacheDirectory != null && cacheDirectory.exists()) {
221                 FileUtils.deleteDirectory(cacheDirectory);
222             }
223             
224             felixRunning = false;
225             felix = null;
226         } catch (IOException e)
227         {
228             throw new OsgiContainerException("Unable to stop OSGi container", e);
229         }
230     }
231 
232     public Bundle[] getBundles()
233     {
234         if (isRunning())
235         {
236             return registration.getBundles();
237         }
238         else
239         {
240             throw new IllegalStateException("Cannot retrieve the bundles if the Felix container isn't running.  Check" +
241                     " earlier in the logs for the possible cause as to why Felix didn't start correctly.");
242         }
243 
244         
245     }
246 
247     public ServiceReference[] getRegisteredServices()
248     {
249         return felix.getRegisteredServices();
250     }
251 
252     public ServiceTracker getServiceTracker(String cls)
253     {
254         if (!isRunning())
255             throw new IllegalStateException("Unable to create a tracker when osgi is not running");
256 
257         ServiceTracker tracker = registration.getServiceTracker(cls, trackers);
258         tracker.open();
259         trackers.add(tracker);
260         return tracker;
261     }
262 
263     public Bundle installBundle(File file) throws OsgiContainerException
264     {
265         try
266         {
267             return registration.install(file, disableMultipleBundleVersions);
268         } catch (BundleException e)
269         {
270             throw new OsgiContainerException("Unable to install bundle", e);
271         }
272     }
273 
274     DefaultComponentRegistrar collectHostComponents(HostComponentProvider provider)
275     {
276         DefaultComponentRegistrar registrar = new DefaultComponentRegistrar();
277         if (provider != null)
278             provider.provide(registrar);
279         return registrar;
280     }
281 
282     void initialiseCacheDirectory() throws OsgiContainerException
283     {
284         try
285         {
286             cacheDirectory = File.createTempFile("felix", null);
287             cacheDirectory.delete();
288             if (cacheDirectory.exists())
289                 FileUtils.deleteDirectory(cacheDirectory);
290 
291             cacheDirectory.mkdir();
292         } catch (IOException e)
293         {
294             throw new OsgiContainerException("Cannot create cache directory", e);
295         }
296     }
297 
298     public boolean isRunning()
299     {
300         return felixRunning;
301     }
302 
303     public List<HostComponentRegistration> getHostComponentRegistrations()
304     {
305         return registration.getHostComponentRegistrations();
306     }
307 
308     private String getAtlassianSpecificOsgiSystemProperty(String originalSystemProperty)
309     {
310         return System.getProperty(ATLASSIAN_PREFIX + originalSystemProperty);
311     }
312 
313     /**
314      * Manages framwork-level framework bundles and host components registration, and individual plugin bundle
315      * installation and removal.
316      */
317     static class BundleRegistration implements BundleActivator, BundleListener
318     {
319         private BundleContext bundleContext;
320         private DefaultComponentRegistrar registrar;
321         private List<ServiceRegistration> hostServicesReferences;
322         private List<HostComponentRegistration> hostComponentRegistrations;
323         private URL frameworkBundlesUrl;
324         private File frameworkBundlesDir;
325         private PackageAdmin packageAdmin;
326 
327         public BundleRegistration(URL frameworkBundlesUrl, File frameworkBundlesDir, DefaultComponentRegistrar registrar)
328         {
329             this.registrar = registrar;
330             this.frameworkBundlesUrl = frameworkBundlesUrl;
331             this.frameworkBundlesDir = frameworkBundlesDir;
332         }
333 
334         public void start(BundleContext context) throws Exception {
335             this.bundleContext = context;
336             ServiceReference ref = context.getServiceReference(org.osgi.service.packageadmin.PackageAdmin.class.getName());
337             packageAdmin = (PackageAdmin) context.getService(ref);
338 
339             context.addBundleListener(this);
340 
341             loadHostComponents(registrar);
342             extractAndInstallFrameworkBundles();
343             context.addFrameworkListener(new FrameworkListener()
344             {
345                 public void frameworkEvent(FrameworkEvent event)
346                 {
347                     String bundleBits = "";
348                     if (event.getBundle() != null)
349                         bundleBits = " in bundle "+event.getBundle().getSymbolicName();
350                     switch (event.getType())
351                     {
352                         case FrameworkEvent.ERROR   : log.error("Framework error"+bundleBits, event.getThrowable());
353                                                       break;
354                         case FrameworkEvent.WARNING : log.warn("Framework warning"+bundleBits, event.getThrowable());
355                                                       break;
356                         case FrameworkEvent.INFO    : log.info("Framework info"+bundleBits, event.getThrowable());
357                                                       break;
358                     }
359                 }
360             });
361         }
362 
363         public void stop(BundleContext ctx) throws Exception {
364         }
365 
366         public void bundleChanged(BundleEvent evt) {
367             switch (evt.getType()) {
368                 case BundleEvent.INSTALLED:
369                     log.info("Installed bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
370                     break;
371                 case BundleEvent.RESOLVED:
372                     log.info("Resolved bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
373                     break;
374                 case BundleEvent.UNRESOLVED:
375                     log.info("Unresolved bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
376                     break;
377                 case BundleEvent.STARTED:
378                     log.info("Started bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
379                     break;
380                 case BundleEvent.STOPPED:
381                     log.info("Stopped bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
382                     break;
383                 case BundleEvent.UNINSTALLED:
384                     log.info("Uninstalled bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
385                     break;
386             }
387         }
388 
389         public Bundle install(File path, boolean uninstallOtherVersions) throws BundleException
390         {
391             if (uninstallOtherVersions)
392             {
393                 try
394                 {
395                     JarFile jar = new JarFile(path);
396                     String name = jar.getManifest().getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
397                     for (Bundle oldBundle : bundleContext.getBundles())
398                     {
399                         if (name.equals(oldBundle.getSymbolicName()))
400                         {
401                             log.info("Uninstalling existing version "+oldBundle.getHeaders().get(Constants.BUNDLE_VERSION));
402                             oldBundle.uninstall();
403 
404                             // Commented out because it results in a race condition for dependent bundles, primarily
405                             // because the default import package setting is optional resolution.
406                             //packageAdmin.refreshPackages(new Bundle[]{oldBundle});
407                         }
408                     }
409 
410                 } catch (IOException e)
411                 {
412                     throw new BundleException("Invalid bundle format", e);
413                 }
414             }
415             Bundle bundle = bundleContext.installBundle(path.toURI().toString());
416             return bundle;
417         }
418 
419         public Bundle[] getBundles()
420         {
421             return bundleContext.getBundles();
422         }
423 
424         public ServiceTracker getServiceTracker(String clazz, final Set<ServiceTracker> trackedTrackers)
425         {
426             return new ServiceTracker(bundleContext, clazz, null){
427                 @Override
428                 public void close()
429                 {
430                     trackedTrackers.remove(this);
431                 }
432             };
433         }
434 
435         public List<HostComponentRegistration> getHostComponentRegistrations()
436         {
437             return hostComponentRegistrations;
438         }
439 
440         private void loadHostComponents(DefaultComponentRegistrar registrar)
441         {
442             // Unregister any existing host components
443             if (hostServicesReferences != null) {
444                 for (ServiceRegistration reg : hostServicesReferences)
445                     reg.unregister();
446             }
447 
448             // Register host components as OSGi services
449             hostServicesReferences = registrar.writeRegistry(bundleContext);
450             hostComponentRegistrations = registrar.getRegistry();
451         }
452 
453         private void extractAndInstallFrameworkBundles() throws BundleException
454         {
455             List<Bundle> bundles = new ArrayList<Bundle>();
456             com.atlassian.plugin.util.FileUtils.conditionallyExtractZipFile(frameworkBundlesUrl, frameworkBundlesDir);
457             for (File bundleFile : frameworkBundlesDir.listFiles(new FilenameFilter() {
458                     public boolean accept(File file, String s) {
459                         return s.endsWith(".jar"); 
460                     }
461                 }))
462             {
463                 bundles.add(install(bundleFile, false));
464             }
465 
466             for (Bundle bundle : bundles)
467             {
468                 bundle.start();
469             }
470         }
471     }
472 
473 }