View Javadoc

1   package com.atlassian.plugin.osgi.container.felix;
2   
3   import com.atlassian.plugin.osgi.container.OsgiContainerException;
4   import com.atlassian.plugin.osgi.container.OsgiContainerManager;
5   import com.atlassian.plugin.osgi.container.PackageScannerConfiguration;
6   import com.atlassian.plugin.osgi.hostcomponents.HostComponentProvider;
7   import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
8   import com.atlassian.plugin.osgi.hostcomponents.impl.DefaultComponentRegistrar;
9   import com.atlassian.plugin.osgi.util.OsgiHeaderUtil;
10  import com.atlassian.plugin.util.ClassLoaderUtils;
11  import org.apache.commons.logging.Log;
12  import org.apache.commons.logging.LogFactory;
13  import org.apache.commons.io.FileUtils;
14  import org.apache.felix.framework.Felix;
15  import org.apache.felix.framework.cache.BundleCache;
16  import org.apache.felix.framework.util.FelixConstants;
17  import org.apache.felix.framework.util.StringMap;
18  import org.osgi.framework.*;
19  import static org.twdata.pkgscanner.PackageScanner.jars;
20  import static org.twdata.pkgscanner.PackageScanner.packages;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.FilenameFilter;
25  import java.net.MalformedURLException;
26  import java.net.URL;
27  import java.util.*;
28  import java.util.jar.JarFile;
29  
30  /**
31   * Felix implementation of the OSGi container manager
32   */
33  public class FelixOsgiContainerManager implements OsgiContainerManager
34  {
35      private static final Log log = LogFactory.getLog(FelixOsgiContainerManager.class);
36      private BundleRegistration registration = null;
37      private Felix felix = null;
38      private boolean felixRunning = false;
39      private File cacheDirectory;
40      private boolean disableMultipleBundleVersions = true;
41  
42      private final URL frameworkBundlesUrl;
43      private PackageScannerConfiguration packageScannerConfig;
44      public static final String OSGI_FRAMEWORK_BUNDLES_ZIP = "osgi-framework-bundles.zip";
45      private File frameworkBundlesDir;
46  
47  
48      public FelixOsgiContainerManager(File frameworkBundlesDir, PackageScannerConfiguration packageScannerConfig)
49      {
50          this(ClassLoaderUtils.getResource(OSGI_FRAMEWORK_BUNDLES_ZIP, FelixOsgiContainerManager.class), frameworkBundlesDir, packageScannerConfig);
51      }
52      public FelixOsgiContainerManager(URL frameworkBundlesZip, File frameworkBundlesDir, PackageScannerConfiguration packageScannerConfig)
53      {
54          if (frameworkBundlesZip == null)
55              throw new IllegalArgumentException("The framework bundles zip is required");
56  
57          this.frameworkBundlesUrl = frameworkBundlesZip;
58          this.packageScannerConfig = packageScannerConfig;
59          this.frameworkBundlesDir = frameworkBundlesDir;
60      }
61  
62      public void setDisableMultipleBundleVersions(boolean val)
63      {
64          this.disableMultipleBundleVersions = val;
65      }
66  
67      public void start(HostComponentProvider provider) throws OsgiContainerException
68      {
69  
70          initialiseCacheDirectory();
71  
72          DefaultComponentRegistrar registrar = collectHostComponents(provider);
73          // Create a case-insensitive configuration property map.
74          final Map configMap = new StringMap(false);
75          // Configure the Felix instance to be embedded.
76          configMap.put(FelixConstants.EMBEDDED_EXECUTION_PROP, "true");
77          // Add the bundle provided service interface package and the core OSGi
78          // packages to be exported from the class path via the system bundle.
79          configMap.put(Constants.FRAMEWORK_SYSTEMPACKAGES, OsgiHeaderUtil.determineExports(registrar.getRegistry(), packageScannerConfig));
80  
81          // Explicitly specify the directory to use for caching bundles.
82          configMap.put(BundleCache.CACHE_PROFILE_DIR_PROP, cacheDirectory.getAbsolutePath());
83  
84          FelixLoggerBridge bridge = new FelixLoggerBridge(log);
85          configMap.put(FelixConstants.LOG_LEVEL_PROP, String.valueOf(bridge.getLogLevel()));
86  
87          try
88          {
89              // Create host activator;
90              registration = new BundleRegistration(frameworkBundlesUrl, frameworkBundlesDir, registrar);
91              final List<BundleActivator> list = new ArrayList<BundleActivator>();
92              list.add(registration);
93  
94              // Now create an instance of the framework with
95              // our configuration properties and activator.
96              felix = new Felix(bridge, configMap, list);
97  
98              // Now start Felix instance.  Starting in a different thread to explicity set daemon status
99              Thread t = new Thread() {
100                 @Override
101                 public void run() {
102                     try
103                     {
104 
105                         felix.start();
106                         felixRunning = true;
107                     } catch (BundleException e)
108                     {
109                         throw new OsgiContainerException("Unable to start felix", e);
110                     }
111                 }
112             };
113             t.setDaemon(true);
114             t.start();
115 
116             // Give it 10 seconds
117             t.join(10 * 60 * 1000);
118 
119 
120 
121         }
122         catch (Exception ex)
123         {
124             throw new OsgiContainerException("Unable to start OSGi container", ex);
125         }
126     }
127 
128     public void stop() throws OsgiContainerException
129     {
130         try
131         {
132             if (felixRunning)
133                 felix.stop();
134             
135             felixRunning = false;
136             felix = null;
137         } catch (BundleException e)
138         {
139             throw new OsgiContainerException("Unable to stop OSGi container", e);
140         }
141     }
142 
143     public Bundle[] getBundles()
144     {
145         return registration.getBundles();
146     }
147 
148     public ServiceReference[] getRegisteredServices()
149     {
150         return felix.getRegisteredServices();
151     }
152 
153     public Bundle installBundle(File file) throws OsgiContainerException
154     {
155         try
156         {
157             return registration.install(file, disableMultipleBundleVersions);
158         } catch (BundleException e)
159         {
160             throw new OsgiContainerException("Unable to install bundle", e);
161         }
162     }
163 
164     public void reloadHostComponents(HostComponentProvider provider)
165     {
166         registration.reloadHostComponents(collectHostComponents(provider));
167     }
168 
169     DefaultComponentRegistrar collectHostComponents(HostComponentProvider provider)
170     {
171         DefaultComponentRegistrar registrar = new DefaultComponentRegistrar();
172         if (provider != null)
173             provider.provide(registrar);
174         return registrar;
175     }
176 
177     void initialiseCacheDirectory() throws OsgiContainerException
178     {
179         try
180         {
181             cacheDirectory = new File(File.createTempFile("foo", "bar").getParentFile(), "felix");
182             if (cacheDirectory.exists())
183                 FileUtils.deleteDirectory(cacheDirectory);
184 
185             cacheDirectory.mkdir();
186             cacheDirectory.deleteOnExit();
187         } catch (IOException e)
188         {
189             throw new OsgiContainerException("Cannot create cache directory", e);
190         }
191     }
192 
193     public boolean isRunning()
194     {
195         return felixRunning;
196     }
197 
198     public List<HostComponentRegistration> getHostComponentRegistrations()
199     {
200         return registration.hostComponentRegistrations;
201     }
202 
203     /**
204      * Manages framwork-level framework bundles and host components registration, and individual plugin bundle
205      * installation and removal.
206      */
207     static class BundleRegistration implements BundleActivator, BundleListener
208     {
209         private BundleContext bundleContext;
210         private DefaultComponentRegistrar registrar;
211         private List<ServiceRegistration> hostServicesReferences;
212         private List<HostComponentRegistration> hostComponentRegistrations;
213         private URL frameworkBundlesUrl;
214         private File frameworkBundlesDir;
215 
216         public BundleRegistration(URL frameworkBundlesUrl, File frameworkBundlesDir, DefaultComponentRegistrar registrar)
217         {
218             this.registrar = registrar;
219             this.frameworkBundlesUrl = frameworkBundlesUrl;
220             this.frameworkBundlesDir = frameworkBundlesDir;
221         }
222 
223         public void start(BundleContext context) throws Exception {
224             this.bundleContext = context;
225             context.addBundleListener(this);
226 
227             reloadHostComponents(registrar);
228             extractAndInstallFrameworkBundles();
229         }
230 
231         public void reloadHostComponents(DefaultComponentRegistrar registrar)
232         {
233             // Unregister any existing host components
234             if (hostServicesReferences != null) {
235                 for (ServiceRegistration reg : hostServicesReferences)
236                     reg.unregister();
237             }
238             
239             // Register host components as OSGi services
240             hostServicesReferences = registrar.writeRegistry(bundleContext);
241             hostComponentRegistrations = registrar.getRegistry();
242         }
243 
244         public void stop(BundleContext ctx) throws Exception {
245         }
246 
247         public void bundleChanged(BundleEvent evt) {
248             switch (evt.getType()) {
249                 case BundleEvent.INSTALLED:
250                     log.warn("Installed bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
251                     break;
252                 case BundleEvent.STARTED:
253                     log.warn("Started bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
254                     break;
255                 case BundleEvent.STOPPED:
256                     log.warn("Stopped bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
257                     break;
258                 case BundleEvent.UNINSTALLED:
259                     log.warn("Uninstalled bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
260                     break;
261             }
262         }
263 
264         public Bundle install(File path, boolean uninstallOtherVersions) throws BundleException
265         {
266             Bundle bundle;
267 
268             if (uninstallOtherVersions)
269             {
270                 try
271                 {
272                     JarFile jar = new JarFile(path);
273                     String name = jar.getManifest().getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
274                     for (Bundle oldBundle : bundleContext.getBundles())
275                     {
276                         if (name.equals(oldBundle.getSymbolicName()))
277                         {
278                             log.info("Uninstalling existing version "+oldBundle.getHeaders().get(Constants.BUNDLE_VERSION));
279                             oldBundle.uninstall();
280                         }
281                     }
282 
283                 } catch (IOException e)
284                 {
285                     throw new BundleException("Invalid bundle format", e);
286                 }
287             }
288             try
289             {
290                 bundle = bundleContext.installBundle(path.toURL().toString());
291             } catch (MalformedURLException e)
292             {
293                 throw new BundleException("Invalid path: "+path);
294             }
295             return bundle;
296         }
297 
298         public Bundle[] getBundles()
299         {
300             return bundleContext.getBundles();
301         }
302 
303         public List<HostComponentRegistration> getHostComponentRegistrations()
304         {
305             return hostComponentRegistrations;
306         }
307 
308         private void extractAndInstallFrameworkBundles() throws IOException, BundleException
309         {
310             List<Bundle> bundles = new ArrayList<Bundle>();
311             com.atlassian.plugin.util.FileUtils.conditionallyExtractZipFile(frameworkBundlesUrl, frameworkBundlesDir);
312             for (File bundleFile : frameworkBundlesDir.listFiles(new FilenameFilter() {
313                     public boolean accept(File file, String s) {
314                         return s.endsWith(".jar"); 
315                     }
316                 }))
317             {
318                 bundles.add(install(bundleFile, false));
319             }
320 
321             for (Bundle bundle : bundles)
322             {
323                 bundle.start();
324             }
325         }
326     }
327 
328 }