View Javadoc

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